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