github.com/lacework-dev/go-moby@v20.10.12+incompatible/daemon/list.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/filters"
    11  	"github.com/docker/docker/container"
    12  	"github.com/docker/docker/daemon/images"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/docker/docker/image"
    15  	"github.com/docker/go-connections/nat"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  var acceptedPsFilterTags = map[string]bool{
    21  	"ancestor":  true,
    22  	"before":    true,
    23  	"exited":    true,
    24  	"id":        true,
    25  	"isolation": true,
    26  	"label":     true,
    27  	"name":      true,
    28  	"status":    true,
    29  	"health":    true,
    30  	"since":     true,
    31  	"volume":    true,
    32  	"network":   true,
    33  	"is-task":   true,
    34  	"publish":   true,
    35  	"expose":    true,
    36  }
    37  
    38  // iterationAction represents possible outcomes happening during the container iteration.
    39  type iterationAction int
    40  
    41  // containerReducer represents a reducer for a container.
    42  // Returns the object to serialize by the api.
    43  type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error)
    44  
    45  const (
    46  	// includeContainer is the action to include a container in the reducer.
    47  	includeContainer iterationAction = iota
    48  	// excludeContainer is the action to exclude a container in the reducer.
    49  	excludeContainer
    50  	// stopIteration is the action to stop iterating over the list of containers.
    51  	stopIteration
    52  )
    53  
    54  // errStopIteration makes the iterator to stop without returning an error.
    55  var errStopIteration = errors.New("container list iteration stopped")
    56  
    57  // List returns an array of all containers registered in the daemon.
    58  func (daemon *Daemon) List() []*container.Container {
    59  	return daemon.containers.List()
    60  }
    61  
    62  // listContext is the daemon generated filtering to iterate over containers.
    63  // This is created based on the user specification from types.ContainerListOptions.
    64  type listContext struct {
    65  	// idx is the container iteration index for this context
    66  	idx int
    67  	// ancestorFilter tells whether it should check ancestors or not
    68  	ancestorFilter bool
    69  	// names is a list of container names to filter with
    70  	names map[string][]string
    71  	// images is a list of images to filter with
    72  	images map[image.ID]bool
    73  	// filters is a collection of arguments to filter with, specified by the user
    74  	filters filters.Args
    75  	// exitAllowed is a list of exit codes allowed to filter with
    76  	exitAllowed []int
    77  
    78  	// beforeFilter is a filter to ignore containers that appear before the one given
    79  	beforeFilter *container.Snapshot
    80  	// sinceFilter is a filter to stop the filtering when the iterator arrives to the given container
    81  	sinceFilter *container.Snapshot
    82  
    83  	// taskFilter tells if we should filter based on whether a container is part of a task
    84  	taskFilter bool
    85  	// isTask tells us if we should filter container that is a task (true) or not (false)
    86  	isTask bool
    87  
    88  	// publish is a list of published ports to filter with
    89  	publish map[nat.Port]bool
    90  	// expose is a list of exposed ports to filter with
    91  	expose map[nat.Port]bool
    92  
    93  	// ContainerListOptions is the filters set by the user
    94  	*types.ContainerListOptions
    95  }
    96  
    97  // byCreatedDescending is a temporary type used to sort a list of containers by creation time.
    98  type byCreatedDescending []container.Snapshot
    99  
   100  func (r byCreatedDescending) Len() int      { return len(r) }
   101  func (r byCreatedDescending) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
   102  func (r byCreatedDescending) Less(i, j int) bool {
   103  	return r[j].CreatedAt.UnixNano() < r[i].CreatedAt.UnixNano()
   104  }
   105  
   106  // Containers returns the list of containers to show given the user's filtering.
   107  func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.Container, error) {
   108  	return daemon.reduceContainers(config, daemon.refreshImage)
   109  }
   110  
   111  func (daemon *Daemon) filterByNameIDMatches(view container.View, ctx *listContext) ([]container.Snapshot, error) {
   112  	idSearch := false
   113  	names := ctx.filters.Get("name")
   114  	ids := ctx.filters.Get("id")
   115  	if len(names)+len(ids) == 0 {
   116  		// if name or ID filters are not in use, return to
   117  		// standard behavior of walking the entire container
   118  		// list from the daemon's in-memory store
   119  		all, err := view.All()
   120  		sort.Sort(byCreatedDescending(all))
   121  		return all, err
   122  	}
   123  
   124  	// idSearch will determine if we limit name matching to the IDs
   125  	// matched from any IDs which were specified as filters
   126  	if len(ids) > 0 {
   127  		idSearch = true
   128  	}
   129  
   130  	matches := make(map[string]bool)
   131  	// find ID matches; errors represent "not found" and can be ignored
   132  	for _, id := range ids {
   133  		if fullID, err := daemon.idIndex.Get(id); err == nil {
   134  			matches[fullID] = true
   135  		}
   136  	}
   137  
   138  	// look for name matches; if ID filtering was used, then limit the
   139  	// search space to the matches map only; errors represent "not found"
   140  	// and can be ignored
   141  	if len(names) > 0 {
   142  		for id, idNames := range ctx.names {
   143  			// if ID filters were used and no matches on that ID were
   144  			// found, continue to next ID in the list
   145  			if idSearch && !matches[id] {
   146  				continue
   147  			}
   148  			for _, eachName := range idNames {
   149  				// match both on container name with, and without slash-prefix
   150  				if ctx.filters.Match("name", eachName) || ctx.filters.Match("name", strings.TrimPrefix(eachName, "/")) {
   151  					matches[id] = true
   152  				}
   153  			}
   154  		}
   155  	}
   156  
   157  	cntrs := make([]container.Snapshot, 0, len(matches))
   158  	for id := range matches {
   159  		c, err := view.Get(id)
   160  		switch err.(type) {
   161  		case nil:
   162  			cntrs = append(cntrs, *c)
   163  		case container.NoSuchContainerError:
   164  			// ignore error
   165  		default:
   166  			return nil, err
   167  		}
   168  	}
   169  
   170  	// Restore sort-order after filtering
   171  	// Created gives us nanosec resolution for sorting
   172  	sort.Sort(byCreatedDescending(cntrs))
   173  
   174  	return cntrs, nil
   175  }
   176  
   177  // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer.
   178  func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) {
   179  	if err := config.Filters.Validate(acceptedPsFilterTags); err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	var (
   184  		view       = daemon.containersReplica.Snapshot()
   185  		containers = []*types.Container{}
   186  	)
   187  
   188  	ctx, err := daemon.foldFilter(view, config)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	// fastpath to only look at a subset of containers if specific name
   194  	// or ID matches were provided by the user--otherwise we potentially
   195  	// end up querying many more containers than intended
   196  	containerList, err := daemon.filterByNameIDMatches(view, ctx)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	for i := range containerList {
   202  		t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer)
   203  		if err != nil {
   204  			if err != errStopIteration {
   205  				return nil, err
   206  			}
   207  			break
   208  		}
   209  		if t != nil {
   210  			containers = append(containers, t)
   211  			ctx.idx++
   212  		}
   213  	}
   214  
   215  	return containers, nil
   216  }
   217  
   218  // reducePsContainer is the basic representation for a container as expected by the ps command.
   219  func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) {
   220  	// filter containers to return
   221  	switch includeContainerInList(container, ctx) {
   222  	case excludeContainer:
   223  		return nil, nil
   224  	case stopIteration:
   225  		return nil, errStopIteration
   226  	}
   227  
   228  	// transform internal container struct into api structs
   229  	newC, err := reducer(container, ctx)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	// release lock because size calculation is slow
   235  	if ctx.Size {
   236  		sizeRw, sizeRootFs := daemon.imageService.GetContainerLayerSize(newC.ID)
   237  		newC.SizeRw = sizeRw
   238  		newC.SizeRootFs = sizeRootFs
   239  	}
   240  	return newC, nil
   241  }
   242  
   243  // foldFilter generates the container filter based on the user's filtering options.
   244  func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerListOptions) (*listContext, error) {
   245  	psFilters := config.Filters
   246  
   247  	var filtExited []int
   248  
   249  	err := psFilters.WalkValues("exited", func(value string) error {
   250  		code, err := strconv.Atoi(value)
   251  		if err != nil {
   252  			return err
   253  		}
   254  		filtExited = append(filtExited, code)
   255  		return nil
   256  	})
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	err = psFilters.WalkValues("status", func(value string) error {
   262  		if !container.IsValidStateString(value) {
   263  			return invalidFilter{"status", value}
   264  		}
   265  
   266  		config.All = true
   267  		return nil
   268  	})
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	var taskFilter, isTask bool
   274  	if psFilters.Contains("is-task") {
   275  		if psFilters.ExactMatch("is-task", "true") {
   276  			taskFilter = true
   277  			isTask = true
   278  		} else if psFilters.ExactMatch("is-task", "false") {
   279  			taskFilter = true
   280  			isTask = false
   281  		} else {
   282  			return nil, invalidFilter{"is-task", psFilters.Get("is-task")}
   283  		}
   284  	}
   285  
   286  	err = psFilters.WalkValues("health", func(value string) error {
   287  		if !container.IsValidHealthString(value) {
   288  			return errdefs.InvalidParameter(errors.Errorf("Unrecognised filter value for health: %s", value))
   289  		}
   290  
   291  		return nil
   292  	})
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	var beforeContFilter, sinceContFilter *container.Snapshot
   298  
   299  	err = psFilters.WalkValues("before", func(value string) error {
   300  		beforeContFilter, err = idOrNameFilter(view, value)
   301  		return err
   302  	})
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	err = psFilters.WalkValues("since", func(value string) error {
   308  		sinceContFilter, err = idOrNameFilter(view, value)
   309  		return err
   310  	})
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	imagesFilter := map[image.ID]bool{}
   316  	var ancestorFilter bool
   317  	if psFilters.Contains("ancestor") {
   318  		ancestorFilter = true
   319  		psFilters.WalkValues("ancestor", func(ancestor string) error {
   320  			img, err := daemon.imageService.GetImage(ancestor, nil)
   321  			if err != nil {
   322  				logrus.Warnf("Error while looking up for image %v", ancestor)
   323  				return nil
   324  			}
   325  			if imagesFilter[img.ID()] {
   326  				// Already seen this ancestor, skip it
   327  				return nil
   328  			}
   329  			// Then walk down the graph and put the imageIds in imagesFilter
   330  			populateImageFilterByParents(imagesFilter, img.ID(), daemon.imageService.Children)
   331  			return nil
   332  		})
   333  	}
   334  
   335  	publishFilter := map[nat.Port]bool{}
   336  	err = psFilters.WalkValues("publish", portOp("publish", publishFilter))
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  
   341  	exposeFilter := map[nat.Port]bool{}
   342  	err = psFilters.WalkValues("expose", portOp("expose", exposeFilter))
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  
   347  	return &listContext{
   348  		filters:              psFilters,
   349  		ancestorFilter:       ancestorFilter,
   350  		images:               imagesFilter,
   351  		exitAllowed:          filtExited,
   352  		beforeFilter:         beforeContFilter,
   353  		sinceFilter:          sinceContFilter,
   354  		taskFilter:           taskFilter,
   355  		isTask:               isTask,
   356  		publish:              publishFilter,
   357  		expose:               exposeFilter,
   358  		ContainerListOptions: config,
   359  		names:                view.GetAllNames(),
   360  	}, nil
   361  }
   362  
   363  func idOrNameFilter(view container.View, value string) (*container.Snapshot, error) {
   364  	filter, err := view.Get(value)
   365  	switch err.(type) {
   366  	case container.NoSuchContainerError:
   367  		// Try name search instead
   368  		found := ""
   369  		for id, idNames := range view.GetAllNames() {
   370  			for _, eachName := range idNames {
   371  				if strings.TrimPrefix(value, "/") == strings.TrimPrefix(eachName, "/") {
   372  					if found != "" && found != id {
   373  						return nil, err
   374  					}
   375  					found = id
   376  				}
   377  			}
   378  		}
   379  		if found != "" {
   380  			filter, err = view.Get(found)
   381  		}
   382  	}
   383  	return filter, err
   384  }
   385  
   386  func portOp(key string, filter map[nat.Port]bool) func(value string) error {
   387  	return func(value string) error {
   388  		if strings.Contains(value, ":") {
   389  			return fmt.Errorf("filter for '%s' should not contain ':': %s", key, value)
   390  		}
   391  		// support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
   392  		proto, port := nat.SplitProtoPort(value)
   393  		start, end, err := nat.ParsePortRange(port)
   394  		if err != nil {
   395  			return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
   396  		}
   397  		for i := start; i <= end; i++ {
   398  			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
   399  			if err != nil {
   400  				return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
   401  			}
   402  			filter[p] = true
   403  		}
   404  		return nil
   405  	}
   406  }
   407  
   408  // includeContainerInList decides whether a container should be included in the output or not based in the filter.
   409  // It also decides if the iteration should be stopped or not.
   410  func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction {
   411  	// Do not include container if it's in the list before the filter container.
   412  	// Set the filter container to nil to include the rest of containers after this one.
   413  	if ctx.beforeFilter != nil {
   414  		if container.ID == ctx.beforeFilter.ID {
   415  			ctx.beforeFilter = nil
   416  		}
   417  		return excludeContainer
   418  	}
   419  
   420  	// Stop iteration when the container arrives to the filter container
   421  	if ctx.sinceFilter != nil {
   422  		if container.ID == ctx.sinceFilter.ID {
   423  			return stopIteration
   424  		}
   425  	}
   426  
   427  	// Do not include container if it's stopped and we're not filters
   428  	if !container.Running && !ctx.All && ctx.Limit <= 0 {
   429  		return excludeContainer
   430  	}
   431  
   432  	// Do not include container if the name doesn't match
   433  	if !ctx.filters.Match("name", container.Name) && !ctx.filters.Match("name", strings.TrimPrefix(container.Name, "/")) {
   434  		return excludeContainer
   435  	}
   436  
   437  	// Do not include container if the id doesn't match
   438  	if !ctx.filters.Match("id", container.ID) {
   439  		return excludeContainer
   440  	}
   441  
   442  	if ctx.taskFilter {
   443  		if ctx.isTask != container.Managed {
   444  			return excludeContainer
   445  		}
   446  	}
   447  
   448  	// Do not include container if any of the labels don't match
   449  	if !ctx.filters.MatchKVList("label", container.Labels) {
   450  		return excludeContainer
   451  	}
   452  
   453  	// Do not include container if isolation doesn't match
   454  	if excludeContainer == excludeByIsolation(container, ctx) {
   455  		return excludeContainer
   456  	}
   457  
   458  	// Stop iteration when the index is over the limit
   459  	if ctx.Limit > 0 && ctx.idx == ctx.Limit {
   460  		return stopIteration
   461  	}
   462  
   463  	// Do not include container if its exit code is not in the filter
   464  	if len(ctx.exitAllowed) > 0 {
   465  		shouldSkip := true
   466  		for _, code := range ctx.exitAllowed {
   467  			if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() {
   468  				shouldSkip = false
   469  				break
   470  			}
   471  		}
   472  		if shouldSkip {
   473  			return excludeContainer
   474  		}
   475  	}
   476  
   477  	// Do not include container if its status doesn't match the filter
   478  	if !ctx.filters.Match("status", container.State) {
   479  		return excludeContainer
   480  	}
   481  
   482  	// Do not include container if its health doesn't match the filter
   483  	if !ctx.filters.ExactMatch("health", container.Health) {
   484  		return excludeContainer
   485  	}
   486  
   487  	if ctx.filters.Contains("volume") {
   488  		volumesByName := make(map[string]types.MountPoint)
   489  		for _, m := range container.Mounts {
   490  			if m.Name != "" {
   491  				volumesByName[m.Name] = m
   492  			} else {
   493  				volumesByName[m.Source] = m
   494  			}
   495  		}
   496  		volumesByDestination := make(map[string]types.MountPoint)
   497  		for _, m := range container.Mounts {
   498  			if m.Destination != "" {
   499  				volumesByDestination[m.Destination] = m
   500  			}
   501  		}
   502  
   503  		volumeExist := fmt.Errorf("volume mounted in container")
   504  		err := ctx.filters.WalkValues("volume", func(value string) error {
   505  			if _, exist := volumesByDestination[value]; exist {
   506  				return volumeExist
   507  			}
   508  			if _, exist := volumesByName[value]; exist {
   509  				return volumeExist
   510  			}
   511  			return nil
   512  		})
   513  		if err != volumeExist {
   514  			return excludeContainer
   515  		}
   516  	}
   517  
   518  	if ctx.ancestorFilter {
   519  		if len(ctx.images) == 0 {
   520  			return excludeContainer
   521  		}
   522  		if !ctx.images[image.ID(container.ImageID)] {
   523  			return excludeContainer
   524  		}
   525  	}
   526  
   527  	var (
   528  		networkExist = errors.New("container part of network")
   529  		noNetworks   = errors.New("container is not part of any networks")
   530  	)
   531  	if ctx.filters.Contains("network") {
   532  		err := ctx.filters.WalkValues("network", func(value string) error {
   533  			if container.NetworkSettings == nil {
   534  				return noNetworks
   535  			}
   536  			if _, ok := container.NetworkSettings.Networks[value]; ok {
   537  				return networkExist
   538  			}
   539  			for _, nw := range container.NetworkSettings.Networks {
   540  				if nw == nil {
   541  					continue
   542  				}
   543  				if strings.HasPrefix(nw.NetworkID, value) {
   544  					return networkExist
   545  				}
   546  			}
   547  			return nil
   548  		})
   549  		if err != networkExist {
   550  			return excludeContainer
   551  		}
   552  	}
   553  
   554  	if len(ctx.expose) > 0 || len(ctx.publish) > 0 {
   555  		var (
   556  			shouldSkip    bool = true
   557  			publishedPort nat.Port
   558  			exposedPort   nat.Port
   559  		)
   560  		for _, port := range container.Ports {
   561  			publishedPort = nat.Port(fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
   562  			exposedPort = nat.Port(fmt.Sprintf("%d/%s", port.PrivatePort, port.Type))
   563  			if ok := ctx.publish[publishedPort]; ok {
   564  				shouldSkip = false
   565  				break
   566  			} else if ok := ctx.expose[exposedPort]; ok {
   567  				shouldSkip = false
   568  				break
   569  			}
   570  		}
   571  		if shouldSkip {
   572  			return excludeContainer
   573  		}
   574  	}
   575  
   576  	return includeContainer
   577  }
   578  
   579  // refreshImage checks if the Image ref still points to the correct ID, and updates the ref to the actual ID when it doesn't
   580  func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*types.Container, error) {
   581  	c := s.Container
   582  	image := s.Image // keep the original ref if still valid (hasn't changed)
   583  	if image != s.ImageID {
   584  		img, err := daemon.imageService.GetImage(image, nil)
   585  		if _, isDNE := err.(images.ErrImageDoesNotExist); err != nil && !isDNE {
   586  			return nil, err
   587  		}
   588  		if err != nil || img.ImageID() != s.ImageID {
   589  			// ref changed, we need to use original ID
   590  			image = s.ImageID
   591  		}
   592  	}
   593  	c.Image = image
   594  	return &c, nil
   595  }
   596  
   597  func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
   598  	if !ancestorMap[imageID] {
   599  		for _, id := range getChildren(imageID) {
   600  			populateImageFilterByParents(ancestorMap, id, getChildren)
   601  		}
   602  		ancestorMap[imageID] = true
   603  	}
   604  }