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