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