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