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 }