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 }