github.com/moby/docker@v26.1.3+incompatible/daemon/list.go (about)

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