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