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