github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/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 if c := daemon.containers.Get(id); c != nil { 161 cntrs = append(cntrs, c) 162 } 163 } 164 165 // Restore sort-order after filtering 166 // Created gives us nanosec resolution for sorting 167 sort.Sort(sort.Reverse(byContainerCreated(cntrs))) 168 169 return cntrs 170 } 171 172 // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer. 173 func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) { 174 containers := []*types.Container{} 175 176 ctx, err := daemon.foldFilter(config) 177 if err != nil { 178 return nil, err 179 } 180 181 // fastpath to only look at a subset of containers if specific name 182 // or ID matches were provided by the user--otherwise we potentially 183 // end up locking and querying many more containers than intended 184 containerList := daemon.filterByNameIDMatches(ctx) 185 186 for _, container := range containerList { 187 t, err := daemon.reducePsContainer(container, ctx, reducer) 188 if err != nil { 189 if err != errStopIteration { 190 return nil, err 191 } 192 break 193 } 194 if t != nil { 195 containers = append(containers, t) 196 ctx.idx++ 197 } 198 } 199 return containers, nil 200 } 201 202 // reducePsContainer is the basic representation for a container as expected by the ps command. 203 func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *listContext, reducer containerReducer) (*types.Container, error) { 204 container.Lock() 205 defer container.Unlock() 206 207 // filter containers to return 208 action := includeContainerInList(container, ctx) 209 switch action { 210 case excludeContainer: 211 return nil, nil 212 case stopIteration: 213 return nil, errStopIteration 214 } 215 216 // transform internal container struct into api structs 217 return reducer(container, ctx) 218 } 219 220 // foldFilter generates the container filter based on the user's filtering options. 221 func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listContext, error) { 222 psFilters := config.Filter 223 224 if err := psFilters.Validate(acceptedPsFilterTags); err != nil { 225 return nil, err 226 } 227 228 var filtExited []int 229 230 err := psFilters.WalkValues("exited", func(value string) error { 231 code, err := strconv.Atoi(value) 232 if err != nil { 233 return err 234 } 235 filtExited = append(filtExited, code) 236 return nil 237 }) 238 if err != nil { 239 return nil, err 240 } 241 242 err = psFilters.WalkValues("status", func(value string) error { 243 if !container.IsValidStateString(value) { 244 return fmt.Errorf("Unrecognised filter value for status: %s", value) 245 } 246 247 config.All = true 248 return nil 249 }) 250 if err != nil { 251 return nil, err 252 } 253 254 var beforeContFilter, sinceContFilter *container.Container 255 256 err = psFilters.WalkValues("before", func(value string) error { 257 beforeContFilter, err = daemon.GetContainer(value) 258 return err 259 }) 260 if err != nil { 261 return nil, err 262 } 263 264 err = psFilters.WalkValues("since", func(value string) error { 265 sinceContFilter, err = daemon.GetContainer(value) 266 return err 267 }) 268 if err != nil { 269 return nil, err 270 } 271 272 imagesFilter := map[image.ID]bool{} 273 var ancestorFilter bool 274 if psFilters.Include("ancestor") { 275 ancestorFilter = true 276 psFilters.WalkValues("ancestor", func(ancestor string) error { 277 id, err := daemon.GetImageID(ancestor) 278 if err != nil { 279 logrus.Warnf("Error while looking up for image %v", ancestor) 280 return nil 281 } 282 if imagesFilter[id] { 283 // Already seen this ancestor, skip it 284 return nil 285 } 286 // Then walk down the graph and put the imageIds in imagesFilter 287 populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children) 288 return nil 289 }) 290 } 291 292 return &listContext{ 293 filters: psFilters, 294 ancestorFilter: ancestorFilter, 295 images: imagesFilter, 296 exitAllowed: filtExited, 297 beforeFilter: beforeContFilter, 298 sinceFilter: sinceContFilter, 299 ContainerListOptions: config, 300 names: daemon.nameIndex.GetAll(), 301 }, nil 302 } 303 304 // includeContainerInList decides whether a container should be included in the output or not based in the filter. 305 // It also decides if the iteration should be stopped or not. 306 func includeContainerInList(container *container.Container, ctx *listContext) iterationAction { 307 // Do not include container if it's in the list before the filter container. 308 // Set the filter container to nil to include the rest of containers after this one. 309 if ctx.beforeFilter != nil { 310 if container.ID == ctx.beforeFilter.ID { 311 ctx.beforeFilter = nil 312 } 313 return excludeContainer 314 } 315 316 // Stop iteration when the container arrives to the filter container 317 if ctx.sinceFilter != nil { 318 if container.ID == ctx.sinceFilter.ID { 319 return stopIteration 320 } 321 } 322 323 // Do not include container if it's stopped and we're not filters 324 if !container.Running && !ctx.All && ctx.Limit <= 0 { 325 return excludeContainer 326 } 327 328 // Do not include container if the name doesn't match 329 if !ctx.filters.Match("name", container.Name) { 330 return excludeContainer 331 } 332 333 // Do not include container if the id doesn't match 334 if !ctx.filters.Match("id", container.ID) { 335 return excludeContainer 336 } 337 338 // Do not include container if any of the labels don't match 339 if !ctx.filters.MatchKVList("label", container.Config.Labels) { 340 return excludeContainer 341 } 342 343 // Do not include container if isolation doesn't match 344 if excludeContainer == excludeByIsolation(container, ctx) { 345 return excludeContainer 346 } 347 348 // Stop iteration when the index is over the limit 349 if ctx.Limit > 0 && ctx.idx == ctx.Limit { 350 return stopIteration 351 } 352 353 // Do not include container if its exit code is not in the filter 354 if len(ctx.exitAllowed) > 0 { 355 shouldSkip := true 356 for _, code := range ctx.exitAllowed { 357 if code == container.ExitCode() && !container.Running && !container.StartedAt.IsZero() { 358 shouldSkip = false 359 break 360 } 361 } 362 if shouldSkip { 363 return excludeContainer 364 } 365 } 366 367 // Do not include container if its status doesn't match the filter 368 if !ctx.filters.Match("status", container.State.StateString()) { 369 return excludeContainer 370 } 371 372 if ctx.filters.Include("volume") { 373 volumesByName := make(map[string]*volume.MountPoint) 374 for _, m := range container.MountPoints { 375 if m.Name != "" { 376 volumesByName[m.Name] = m 377 } else { 378 volumesByName[m.Source] = m 379 } 380 } 381 382 volumeExist := fmt.Errorf("volume mounted in container") 383 err := ctx.filters.WalkValues("volume", func(value string) error { 384 if _, exist := container.MountPoints[value]; exist { 385 return volumeExist 386 } 387 if _, exist := volumesByName[value]; exist { 388 return volumeExist 389 } 390 return nil 391 }) 392 if err != volumeExist { 393 return excludeContainer 394 } 395 } 396 397 if ctx.ancestorFilter { 398 if len(ctx.images) == 0 { 399 return excludeContainer 400 } 401 if !ctx.images[container.ImageID] { 402 return excludeContainer 403 } 404 } 405 406 networkExist := fmt.Errorf("container part of network") 407 if ctx.filters.Include("network") { 408 err := ctx.filters.WalkValues("network", func(value string) error { 409 if _, ok := container.NetworkSettings.Networks[value]; ok { 410 return networkExist 411 } 412 for _, nw := range container.NetworkSettings.Networks { 413 if nw.NetworkID == value { 414 return networkExist 415 } 416 } 417 return nil 418 }) 419 if err != networkExist { 420 return excludeContainer 421 } 422 } 423 424 return includeContainer 425 } 426 427 // transformContainer generates the container type expected by the docker ps command. 428 func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) { 429 newC := &types.Container{ 430 ID: container.ID, 431 Names: ctx.names[container.ID], 432 ImageID: container.ImageID.String(), 433 } 434 if newC.Names == nil { 435 // Dead containers will often have no name, so make sure the response isn't null 436 newC.Names = []string{} 437 } 438 439 image := container.Config.Image // if possible keep the original ref 440 if image != container.ImageID.String() { 441 id, err := daemon.GetImageID(image) 442 if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE { 443 return nil, err 444 } 445 if err != nil || id != container.ImageID { 446 image = container.ImageID.String() 447 } 448 } 449 newC.Image = image 450 451 if len(container.Args) > 0 { 452 args := []string{} 453 for _, arg := range container.Args { 454 if strings.Contains(arg, " ") { 455 args = append(args, fmt.Sprintf("'%s'", arg)) 456 } else { 457 args = append(args, arg) 458 } 459 } 460 argsAsString := strings.Join(args, " ") 461 462 newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) 463 } else { 464 newC.Command = container.Path 465 } 466 newC.Created = container.Created.Unix() 467 newC.State = container.State.StateString() 468 newC.Status = container.State.String() 469 newC.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode) 470 // copy networks to avoid races 471 networks := make(map[string]*networktypes.EndpointSettings) 472 for name, network := range container.NetworkSettings.Networks { 473 if network == nil { 474 continue 475 } 476 networks[name] = &networktypes.EndpointSettings{ 477 EndpointID: network.EndpointID, 478 Gateway: network.Gateway, 479 IPAddress: network.IPAddress, 480 IPPrefixLen: network.IPPrefixLen, 481 IPv6Gateway: network.IPv6Gateway, 482 GlobalIPv6Address: network.GlobalIPv6Address, 483 GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen, 484 MacAddress: network.MacAddress, 485 NetworkID: network.NetworkID, 486 } 487 if network.IPAMConfig != nil { 488 networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{ 489 IPv4Address: network.IPAMConfig.IPv4Address, 490 IPv6Address: network.IPAMConfig.IPv6Address, 491 } 492 } 493 } 494 newC.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks} 495 496 newC.Ports = []types.Port{} 497 for port, bindings := range container.NetworkSettings.Ports { 498 p, err := nat.ParsePort(port.Port()) 499 if err != nil { 500 return nil, err 501 } 502 if len(bindings) == 0 { 503 newC.Ports = append(newC.Ports, types.Port{ 504 PrivatePort: p, 505 Type: port.Proto(), 506 }) 507 continue 508 } 509 for _, binding := range bindings { 510 h, err := nat.ParsePort(binding.HostPort) 511 if err != nil { 512 return nil, err 513 } 514 newC.Ports = append(newC.Ports, types.Port{ 515 PrivatePort: p, 516 PublicPort: h, 517 Type: port.Proto(), 518 IP: binding.HostIP, 519 }) 520 } 521 } 522 523 if ctx.Size { 524 sizeRw, sizeRootFs := daemon.getSize(container) 525 newC.SizeRw = sizeRw 526 newC.SizeRootFs = sizeRootFs 527 } 528 newC.Labels = container.Config.Labels 529 newC.Mounts = addMountPoints(container) 530 531 return newC, nil 532 } 533 534 // Volumes lists known volumes, using the filter to restrict the range 535 // of volumes returned. 536 func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) { 537 var ( 538 volumesOut []*types.Volume 539 ) 540 volFilters, err := filters.FromParam(filter) 541 if err != nil { 542 return nil, nil, err 543 } 544 545 if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil { 546 return nil, nil, err 547 } 548 549 volumes, warnings, err := daemon.volumes.List() 550 if err != nil { 551 return nil, nil, err 552 } 553 554 filterVolumes, err := daemon.filterVolumes(volumes, volFilters) 555 if err != nil { 556 return nil, nil, err 557 } 558 for _, v := range filterVolumes { 559 apiV := volumeToAPIType(v) 560 if vv, ok := v.(interface { 561 CachedPath() string 562 }); ok { 563 apiV.Mountpoint = vv.CachedPath() 564 } else { 565 apiV.Mountpoint = v.Path() 566 } 567 volumesOut = append(volumesOut, apiV) 568 } 569 return volumesOut, warnings, nil 570 } 571 572 // filterVolumes filters volume list according to user specified filter 573 // and returns user chosen volumes 574 func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) ([]volume.Volume, error) { 575 // if filter is empty, return original volume list 576 if filter.Len() == 0 { 577 return vols, nil 578 } 579 580 var retVols []volume.Volume 581 for _, vol := range vols { 582 if filter.Include("name") { 583 if !filter.Match("name", vol.Name()) { 584 continue 585 } 586 } 587 if filter.Include("driver") { 588 if !filter.Match("driver", vol.DriverName()) { 589 continue 590 } 591 } 592 retVols = append(retVols, vol) 593 } 594 danglingOnly := false 595 if filter.Include("dangling") { 596 if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") { 597 danglingOnly = true 598 } else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") { 599 return nil, fmt.Errorf("Invalid filter 'dangling=%s'", filter.Get("dangling")) 600 } 601 retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly) 602 } 603 return retVols, nil 604 } 605 606 func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) { 607 if !ancestorMap[imageID] { 608 for _, id := range getChildren(imageID) { 609 populateImageFilterByParents(ancestorMap, id, getChildren) 610 } 611 ancestorMap[imageID] = true 612 } 613 }