github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/daemon/list.go (about) 1 package daemon 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strconv" 8 "strings" 9 10 "github.com/Sirupsen/logrus" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/filters" 13 networktypes "github.com/docker/docker/api/types/network" 14 "github.com/docker/docker/container" 15 "github.com/docker/docker/image" 16 "github.com/docker/docker/volume" 17 "github.com/docker/go-connections/nat" 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.Container, *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.Container 87 // sinceFilter is a filter to stop the filtering when the iterator arrive to the given container 88 sinceFilter *container.Container 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 // byContainerCreated is a temporary type used to sort a list of containers by creation time. 105 type byContainerCreated []*container.Container 106 107 func (r byContainerCreated) Len() int { return len(r) } 108 func (r byContainerCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } 109 func (r byContainerCreated) Less(i, j int) bool { 110 return r[i].Created.UnixNano() < r[j].Created.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.transformContainer) 116 } 117 118 func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Container { 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 return daemon.List() 127 } 128 129 // idSearch will determine if we limit name matching to the IDs 130 // matched from any IDs which were specified as filters 131 if len(ids) > 0 { 132 idSearch = true 133 } 134 135 matches := make(map[string]bool) 136 // find ID matches; errors represent "not found" and can be ignored 137 for _, id := range ids { 138 if fullID, err := daemon.idIndex.Get(id); err == nil { 139 matches[fullID] = true 140 } 141 } 142 143 // look for name matches; if ID filtering was used, then limit the 144 // search space to the matches map only; errors represent "not found" 145 // and can be ignored 146 if len(names) > 0 { 147 for id, idNames := range ctx.names { 148 // if ID filters were used and no matches on that ID were 149 // found, continue to next ID in the list 150 if idSearch && !matches[id] { 151 continue 152 } 153 for _, eachName := range idNames { 154 if ctx.filters.Match("name", eachName) { 155 matches[id] = true 156 } 157 } 158 } 159 } 160 161 cntrs := make([]*container.Container, 0, len(matches)) 162 for id := range matches { 163 if c := daemon.containers.Get(id); c != nil { 164 cntrs = append(cntrs, c) 165 } 166 } 167 168 // Restore sort-order after filtering 169 // Created gives us nanosec resolution for sorting 170 sort.Sort(sort.Reverse(byContainerCreated(cntrs))) 171 172 return cntrs 173 } 174 175 // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer. 176 func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) { 177 var ( 178 containers = []*types.Container{} 179 ) 180 181 ctx, err := daemon.foldFilter(config) 182 if err != nil { 183 return nil, err 184 } 185 186 // fastpath to only look at a subset of containers if specific name 187 // or ID matches were provided by the user--otherwise we potentially 188 // end up locking and querying many more containers than intended 189 containerList := daemon.filterByNameIDMatches(ctx) 190 191 for _, container := range containerList { 192 t, err := daemon.reducePsContainer(container, ctx, reducer) 193 if err != nil { 194 if err != errStopIteration { 195 return nil, err 196 } 197 break 198 } 199 if t != nil { 200 containers = append(containers, t) 201 ctx.idx++ 202 } 203 } 204 205 return containers, nil 206 } 207 208 // reducePsContainer is the basic representation for a container as expected by the ps command. 209 func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *listContext, reducer containerReducer) (*types.Container, error) { 210 container.Lock() 211 212 // filter containers to return 213 action := includeContainerInList(container, ctx) 214 switch action { 215 case excludeContainer: 216 container.Unlock() 217 return nil, nil 218 case stopIteration: 219 container.Unlock() 220 return nil, errStopIteration 221 } 222 223 // transform internal container struct into api structs 224 newC, err := reducer(container, ctx) 225 container.Unlock() 226 if err != nil { 227 return nil, err 228 } 229 230 // release lock because size calculation is slow 231 if ctx.Size { 232 sizeRw, sizeRootFs := daemon.getSize(newC.ID) 233 newC.SizeRw = sizeRw 234 newC.SizeRootFs = sizeRootFs 235 } 236 return newC, nil 237 } 238 239 // foldFilter generates the container filter based on the user's filtering options. 240 func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listContext, error) { 241 psFilters := config.Filters 242 243 if err := psFilters.Validate(acceptedPsFilterTags); err != nil { 244 return nil, err 245 } 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 fmt.Errorf("Unrecognised filter value for status: %s", 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.Include("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, fmt.Errorf("Invalid filter 'is-task=%s'", psFilters.Get("is-task")) 283 } 284 } 285 286 err = psFilters.WalkValues("health", func(value string) error { 287 if !container.IsValidHealthString(value) { 288 return fmt.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.Container 298 299 err = psFilters.WalkValues("before", func(value string) error { 300 beforeContFilter, err = daemon.GetContainer(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 = daemon.GetContainer(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.Include("ancestor") { 318 ancestorFilter = true 319 psFilters.WalkValues("ancestor", func(ancestor string) error { 320 id, err := daemon.GetImageID(ancestor) 321 if err != nil { 322 logrus.Warnf("Error while looking up for image %v", ancestor) 323 return nil 324 } 325 if imagesFilter[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, id, daemon.imageStore.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: daemon.nameIndex.GetAll(), 360 }, nil 361 } 362 func portOp(key string, filter map[nat.Port]bool) func(value string) error { 363 return func(value string) error { 364 if strings.Contains(value, ":") { 365 return fmt.Errorf("filter for '%s' should not contain ':': %s", key, value) 366 } 367 //support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] 368 proto, port := nat.SplitProtoPort(value) 369 start, end, err := nat.ParsePortRange(port) 370 if err != nil { 371 return fmt.Errorf("error while looking up for %s %s: %s", key, value, err) 372 } 373 for i := start; i <= end; i++ { 374 p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) 375 if err != nil { 376 return fmt.Errorf("error while looking up for %s %s: %s", key, value, err) 377 } 378 filter[p] = true 379 } 380 return nil 381 } 382 } 383 384 // includeContainerInList decides whether a container should be included in the output or not based in the filter. 385 // It also decides if the iteration should be stopped or not. 386 func includeContainerInList(container *container.Container, ctx *listContext) iterationAction { 387 // Do not include container if it's in the list before the filter container. 388 // Set the filter container to nil to include the rest of containers after this one. 389 if ctx.beforeFilter != nil { 390 if container.ID == ctx.beforeFilter.ID { 391 ctx.beforeFilter = nil 392 } 393 return excludeContainer 394 } 395 396 // Stop iteration when the container arrives to the filter container 397 if ctx.sinceFilter != nil { 398 if container.ID == ctx.sinceFilter.ID { 399 return stopIteration 400 } 401 } 402 403 // Do not include container if it's stopped and we're not filters 404 if !container.Running && !ctx.All && ctx.Limit <= 0 { 405 return excludeContainer 406 } 407 408 // Do not include container if the name doesn't match 409 if !ctx.filters.Match("name", container.Name) { 410 return excludeContainer 411 } 412 413 // Do not include container if the id doesn't match 414 if !ctx.filters.Match("id", container.ID) { 415 return excludeContainer 416 } 417 418 if ctx.taskFilter { 419 if ctx.isTask != container.Managed { 420 return excludeContainer 421 } 422 } 423 424 // Do not include container if any of the labels don't match 425 if !ctx.filters.MatchKVList("label", container.Config.Labels) { 426 return excludeContainer 427 } 428 429 // Do not include container if isolation doesn't match 430 if excludeContainer == excludeByIsolation(container, ctx) { 431 return excludeContainer 432 } 433 434 // Stop iteration when the index is over the limit 435 if ctx.Limit > 0 && ctx.idx == ctx.Limit { 436 return stopIteration 437 } 438 439 // Do not include container if its exit code is not in the filter 440 if len(ctx.exitAllowed) > 0 { 441 shouldSkip := true 442 for _, code := range ctx.exitAllowed { 443 if code == container.ExitCode() && !container.Running && !container.StartedAt.IsZero() { 444 shouldSkip = false 445 break 446 } 447 } 448 if shouldSkip { 449 return excludeContainer 450 } 451 } 452 453 // Do not include container if its status doesn't match the filter 454 if !ctx.filters.Match("status", container.State.StateString()) { 455 return excludeContainer 456 } 457 458 // Do not include container if its health doesn't match the filter 459 if !ctx.filters.ExactMatch("health", container.State.HealthString()) { 460 return excludeContainer 461 } 462 463 if ctx.filters.Include("volume") { 464 volumesByName := make(map[string]*volume.MountPoint) 465 for _, m := range container.MountPoints { 466 if m.Name != "" { 467 volumesByName[m.Name] = m 468 } else { 469 volumesByName[m.Source] = m 470 } 471 } 472 473 volumeExist := fmt.Errorf("volume mounted in container") 474 err := ctx.filters.WalkValues("volume", func(value string) error { 475 if _, exist := container.MountPoints[value]; exist { 476 return volumeExist 477 } 478 if _, exist := volumesByName[value]; exist { 479 return volumeExist 480 } 481 return nil 482 }) 483 if err != volumeExist { 484 return excludeContainer 485 } 486 } 487 488 if ctx.ancestorFilter { 489 if len(ctx.images) == 0 { 490 return excludeContainer 491 } 492 if !ctx.images[container.ImageID] { 493 return excludeContainer 494 } 495 } 496 497 networkExist := fmt.Errorf("container part of network") 498 if ctx.filters.Include("network") { 499 err := ctx.filters.WalkValues("network", func(value string) error { 500 if _, ok := container.NetworkSettings.Networks[value]; ok { 501 return networkExist 502 } 503 for _, nw := range container.NetworkSettings.Networks { 504 if nw.EndpointSettings == nil { 505 continue 506 } 507 if strings.HasPrefix(nw.NetworkID, value) { 508 return networkExist 509 } 510 } 511 return nil 512 }) 513 if err != networkExist { 514 return excludeContainer 515 } 516 } 517 518 if len(ctx.publish) > 0 { 519 shouldSkip := true 520 for port := range ctx.publish { 521 if _, ok := container.HostConfig.PortBindings[port]; ok { 522 shouldSkip = false 523 break 524 } 525 } 526 if shouldSkip { 527 return excludeContainer 528 } 529 } 530 531 if len(ctx.expose) > 0 { 532 shouldSkip := true 533 for port := range ctx.expose { 534 if _, ok := container.Config.ExposedPorts[port]; ok { 535 shouldSkip = false 536 break 537 } 538 } 539 if shouldSkip { 540 return excludeContainer 541 } 542 } 543 544 return includeContainer 545 } 546 547 // transformContainer generates the container type expected by the docker ps command. 548 func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) { 549 newC := &types.Container{ 550 ID: container.ID, 551 Names: ctx.names[container.ID], 552 ImageID: container.ImageID.String(), 553 } 554 if newC.Names == nil { 555 // Dead containers will often have no name, so make sure the response isn't null 556 newC.Names = []string{} 557 } 558 559 image := container.Config.Image // if possible keep the original ref 560 if image != container.ImageID.String() { 561 id, err := daemon.GetImageID(image) 562 if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE { 563 return nil, err 564 } 565 if err != nil || id != container.ImageID { 566 image = container.ImageID.String() 567 } 568 } 569 newC.Image = image 570 571 if len(container.Args) > 0 { 572 args := []string{} 573 for _, arg := range container.Args { 574 if strings.Contains(arg, " ") { 575 args = append(args, fmt.Sprintf("'%s'", arg)) 576 } else { 577 args = append(args, arg) 578 } 579 } 580 argsAsString := strings.Join(args, " ") 581 582 newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) 583 } else { 584 newC.Command = container.Path 585 } 586 newC.Created = container.Created.Unix() 587 newC.State = container.State.StateString() 588 newC.Status = container.State.String() 589 newC.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode) 590 // copy networks to avoid races 591 networks := make(map[string]*networktypes.EndpointSettings) 592 for name, network := range container.NetworkSettings.Networks { 593 if network == nil || network.EndpointSettings == nil { 594 continue 595 } 596 networks[name] = &networktypes.EndpointSettings{ 597 EndpointID: network.EndpointID, 598 Gateway: network.Gateway, 599 IPAddress: network.IPAddress, 600 IPPrefixLen: network.IPPrefixLen, 601 IPv6Gateway: network.IPv6Gateway, 602 GlobalIPv6Address: network.GlobalIPv6Address, 603 GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen, 604 MacAddress: network.MacAddress, 605 NetworkID: network.NetworkID, 606 } 607 if network.IPAMConfig != nil { 608 networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{ 609 IPv4Address: network.IPAMConfig.IPv4Address, 610 IPv6Address: network.IPAMConfig.IPv6Address, 611 } 612 } 613 } 614 newC.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks} 615 616 newC.Ports = []types.Port{} 617 for port, bindings := range container.NetworkSettings.Ports { 618 p, err := nat.ParsePort(port.Port()) 619 if err != nil { 620 return nil, err 621 } 622 if len(bindings) == 0 { 623 newC.Ports = append(newC.Ports, types.Port{ 624 PrivatePort: uint16(p), 625 Type: port.Proto(), 626 }) 627 continue 628 } 629 for _, binding := range bindings { 630 h, err := nat.ParsePort(binding.HostPort) 631 if err != nil { 632 return nil, err 633 } 634 newC.Ports = append(newC.Ports, types.Port{ 635 PrivatePort: uint16(p), 636 PublicPort: uint16(h), 637 Type: port.Proto(), 638 IP: binding.HostIP, 639 }) 640 } 641 } 642 643 newC.Labels = container.Config.Labels 644 newC.Mounts = addMountPoints(container) 645 646 return newC, nil 647 } 648 649 // Volumes lists known volumes, using the filter to restrict the range 650 // of volumes returned. 651 func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) { 652 var ( 653 volumesOut []*types.Volume 654 ) 655 volFilters, err := filters.FromParam(filter) 656 if err != nil { 657 return nil, nil, err 658 } 659 660 if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil { 661 return nil, nil, err 662 } 663 664 volumes, warnings, err := daemon.volumes.List() 665 if err != nil { 666 return nil, nil, err 667 } 668 669 filterVolumes, err := daemon.filterVolumes(volumes, volFilters) 670 if err != nil { 671 return nil, nil, err 672 } 673 for _, v := range filterVolumes { 674 apiV := volumeToAPIType(v) 675 if vv, ok := v.(interface { 676 CachedPath() string 677 }); ok { 678 apiV.Mountpoint = vv.CachedPath() 679 } else { 680 apiV.Mountpoint = v.Path() 681 } 682 volumesOut = append(volumesOut, apiV) 683 } 684 return volumesOut, warnings, nil 685 } 686 687 // filterVolumes filters volume list according to user specified filter 688 // and returns user chosen volumes 689 func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ([]volume.Volume, error) { 690 // if filter is empty, return original volume list 691 if filter.Len() == 0 { 692 return vols, nil 693 } 694 695 var retVols []volume.Volume 696 for _, vol := range vols { 697 if filter.Include("name") { 698 if !filter.Match("name", vol.Name()) { 699 continue 700 } 701 } 702 if filter.Include("driver") { 703 if !filter.ExactMatch("driver", vol.DriverName()) { 704 continue 705 } 706 } 707 if filter.Include("label") { 708 v, ok := vol.(volume.DetailedVolume) 709 if !ok { 710 continue 711 } 712 if !filter.MatchKVList("label", v.Labels()) { 713 continue 714 } 715 } 716 retVols = append(retVols, vol) 717 } 718 danglingOnly := false 719 if filter.Include("dangling") { 720 if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") { 721 danglingOnly = true 722 } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") { 723 return nil, fmt.Errorf("Invalid filter 'dangling=%s'", filter.Get("dangling")) 724 } 725 retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly) 726 } 727 return retVols, nil 728 } 729 730 func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) { 731 if !ancestorMap[imageID] { 732 for _, id := range getChildren(imageID) { 733 populateImageFilterByParents(ancestorMap, id, getChildren) 734 } 735 ancestorMap[imageID] = true 736 } 737 }