github.com/ruphin/docker@v1.10.1/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/engine-api/types" 13 "github.com/docker/engine-api/types/filters" 14 networktypes "github.com/docker/engine-api/types/network" 15 "github.com/docker/go-connections/nat" 16 ) 17 18 var acceptedVolumeFilterTags = map[string]bool{ 19 "dangling": true, 20 } 21 22 // iterationAction represents possible outcomes happening during the container iteration. 23 type iterationAction int 24 25 // containerReducer represents a reducer for a container. 26 // Returns the object to serialize by the api. 27 type containerReducer func(*container.Container, *listContext) (*types.Container, error) 28 29 const ( 30 // includeContainer is the action to include a container in the reducer. 31 includeContainer iterationAction = iota 32 // excludeContainer is the action to exclude a container in the reducer. 33 excludeContainer 34 // stopIteration is the action to stop iterating over the list of containers. 35 stopIteration 36 ) 37 38 // errStopIteration makes the iterator to stop without returning an error. 39 var errStopIteration = errors.New("container list iteration stopped") 40 41 // List returns an array of all containers registered in the daemon. 42 func (daemon *Daemon) List() []*container.Container { 43 return daemon.containers.List() 44 } 45 46 // ContainersConfig is the filtering specified by the user to iterate over containers. 47 type ContainersConfig struct { 48 // if true show all containers, otherwise only running containers. 49 All bool 50 // show all containers created after this container id 51 Since string 52 // show all containers created before this container id 53 Before string 54 // number of containers to return at most 55 Limit int 56 // if true include the sizes of the containers 57 Size bool 58 // return only containers that match filters 59 Filters string 60 } 61 62 // listContext is the daemon generated filtering to iterate over containers. 63 // This is created based on the user specification. 64 type listContext struct { 65 // idx is the container iteration index for this context 66 idx int 67 // ancestorFilter tells whether it should check ancestors or not 68 ancestorFilter bool 69 // names is a list of container names to filter with 70 names map[string][]string 71 // images is a list of images to filter with 72 images map[image.ID]bool 73 // filters is a collection of arguments to filter with, specified by the user 74 filters filters.Args 75 // exitAllowed is a list of exit codes allowed to filter with 76 exitAllowed []int 77 78 // FIXME Remove this for 1.12 as --since and --before are deprecated 79 // beforeContainer is a filter to ignore containers that appear before the one given 80 beforeContainer *container.Container 81 // sinceContainer is a filter to stop the filtering when the iterator arrive to the given container 82 sinceContainer *container.Container 83 84 // beforeFilter is a filter to ignore containers that appear before the one given 85 // this is used for --filter=before= and --before=, the latter is deprecated. 86 beforeFilter *container.Container 87 // sinceFilter is a filter to stop the filtering when the iterator arrive to the given container 88 // this is used for --filter=since= and --since=, the latter is deprecated. 89 sinceFilter *container.Container 90 // ContainersConfig is the filters set by the user 91 *ContainersConfig 92 } 93 94 // Containers returns the list of containers to show given the user's filtering. 95 func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, error) { 96 return daemon.reduceContainers(config, daemon.transformContainer) 97 } 98 99 // reduceContainer parses the user filtering and generates the list of containers to return based on a reducer. 100 func (daemon *Daemon) reduceContainers(config *ContainersConfig, reducer containerReducer) ([]*types.Container, error) { 101 containers := []*types.Container{} 102 103 ctx, err := daemon.foldFilter(config) 104 if err != nil { 105 return nil, err 106 } 107 108 for _, container := range daemon.List() { 109 t, err := daemon.reducePsContainer(container, ctx, reducer) 110 if err != nil { 111 if err != errStopIteration { 112 return nil, err 113 } 114 break 115 } 116 if t != nil { 117 containers = append(containers, t) 118 ctx.idx++ 119 } 120 } 121 return containers, nil 122 } 123 124 // reducePsContainer is the basic representation for a container as expected by the ps command. 125 func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *listContext, reducer containerReducer) (*types.Container, error) { 126 container.Lock() 127 defer container.Unlock() 128 129 // filter containers to return 130 action := includeContainerInList(container, ctx) 131 switch action { 132 case excludeContainer: 133 return nil, nil 134 case stopIteration: 135 return nil, errStopIteration 136 } 137 138 // transform internal container struct into api structs 139 return reducer(container, ctx) 140 } 141 142 // foldFilter generates the container filter based in the user's filtering options. 143 func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) { 144 psFilters, err := filters.FromParam(config.Filters) 145 if err != nil { 146 return nil, err 147 } 148 149 var filtExited []int 150 err = psFilters.WalkValues("exited", func(value string) error { 151 code, err := strconv.Atoi(value) 152 if err != nil { 153 return err 154 } 155 filtExited = append(filtExited, code) 156 return nil 157 }) 158 if err != nil { 159 return nil, err 160 } 161 162 err = psFilters.WalkValues("status", func(value string) error { 163 if !container.IsValidStateString(value) { 164 return fmt.Errorf("Unrecognised filter value for status: %s", value) 165 } 166 167 config.All = true 168 return nil 169 }) 170 if err != nil { 171 return nil, err 172 } 173 174 var beforeContFilter, sinceContFilter *container.Container 175 // FIXME remove this for 1.12 as --since and --before are deprecated 176 var beforeContainer, sinceContainer *container.Container 177 178 err = psFilters.WalkValues("before", func(value string) error { 179 beforeContFilter, err = daemon.GetContainer(value) 180 return err 181 }) 182 if err != nil { 183 return nil, err 184 } 185 186 err = psFilters.WalkValues("since", func(value string) error { 187 sinceContFilter, err = daemon.GetContainer(value) 188 return err 189 }) 190 if err != nil { 191 return nil, err 192 } 193 194 imagesFilter := map[image.ID]bool{} 195 var ancestorFilter bool 196 if psFilters.Include("ancestor") { 197 ancestorFilter = true 198 psFilters.WalkValues("ancestor", func(ancestor string) error { 199 id, err := daemon.GetImageID(ancestor) 200 if err != nil { 201 logrus.Warnf("Error while looking up for image %v", ancestor) 202 return nil 203 } 204 if imagesFilter[id] { 205 // Already seen this ancestor, skip it 206 return nil 207 } 208 // Then walk down the graph and put the imageIds in imagesFilter 209 populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children) 210 return nil 211 }) 212 } 213 214 // FIXME remove this for 1.12 as --since and --before are deprecated 215 if config.Before != "" { 216 beforeContainer, err = daemon.GetContainer(config.Before) 217 if err != nil { 218 return nil, err 219 } 220 } 221 222 // FIXME remove this for 1.12 as --since and --before are deprecated 223 if config.Since != "" { 224 sinceContainer, err = daemon.GetContainer(config.Since) 225 if err != nil { 226 return nil, err 227 } 228 } 229 230 return &listContext{ 231 filters: psFilters, 232 ancestorFilter: ancestorFilter, 233 images: imagesFilter, 234 exitAllowed: filtExited, 235 beforeContainer: beforeContainer, 236 sinceContainer: sinceContainer, 237 beforeFilter: beforeContFilter, 238 sinceFilter: sinceContFilter, 239 ContainersConfig: config, 240 names: daemon.nameIndex.GetAll(), 241 }, nil 242 } 243 244 // includeContainerInList decides whether a containers should be include in the output or not based in the filter. 245 // It also decides if the iteration should be stopped or not. 246 func includeContainerInList(container *container.Container, ctx *listContext) iterationAction { 247 // Do not include container if it's stopped and we're not filters 248 // FIXME remove the ctx.beforContainer part of the condition for 1.12 as --since and --before are deprecated 249 if !container.Running && !ctx.All && ctx.Limit <= 0 && ctx.beforeContainer == nil && ctx.sinceContainer == nil { 250 return excludeContainer 251 } 252 253 // Do not include container if the name doesn't match 254 if !ctx.filters.Match("name", container.Name) { 255 return excludeContainer 256 } 257 258 // Do not include container if the id doesn't match 259 if !ctx.filters.Match("id", container.ID) { 260 return excludeContainer 261 } 262 263 // Do not include container if any of the labels don't match 264 if !ctx.filters.MatchKVList("label", container.Config.Labels) { 265 return excludeContainer 266 } 267 268 // Do not include container if the isolation mode doesn't match 269 if excludeContainer == excludeByIsolation(container, ctx) { 270 return excludeContainer 271 } 272 273 // FIXME remove this for 1.12 as --since and --before are deprecated 274 if ctx.beforeContainer != nil { 275 if container.ID == ctx.beforeContainer.ID { 276 ctx.beforeContainer = nil 277 } 278 return excludeContainer 279 } 280 281 // FIXME remove this for 1.12 as --since and --before are deprecated 282 if ctx.sinceContainer != nil { 283 if container.ID == ctx.sinceContainer.ID { 284 return stopIteration 285 } 286 } 287 288 // Do not include container if it's in the list before the filter container. 289 // Set the filter container to nil to include the rest of containers after this one. 290 if ctx.beforeFilter != nil { 291 if container.ID == ctx.beforeFilter.ID { 292 ctx.beforeFilter = nil 293 } 294 return excludeContainer 295 } 296 297 // Stop iteration when the container arrives to the filter container 298 if ctx.sinceFilter != nil { 299 if container.ID == ctx.sinceFilter.ID { 300 return stopIteration 301 } 302 } 303 304 // Stop iteration when the index is over the limit 305 if ctx.Limit > 0 && ctx.idx == ctx.Limit { 306 return stopIteration 307 } 308 309 // Do not include container if its exit code is not in the filter 310 if len(ctx.exitAllowed) > 0 { 311 shouldSkip := true 312 for _, code := range ctx.exitAllowed { 313 if code == container.ExitCode && !container.Running { 314 shouldSkip = false 315 break 316 } 317 } 318 if shouldSkip { 319 return excludeContainer 320 } 321 } 322 323 // Do not include container if its status doesn't match the filter 324 if !ctx.filters.Match("status", container.State.StateString()) { 325 return excludeContainer 326 } 327 328 if ctx.ancestorFilter { 329 if len(ctx.images) == 0 { 330 return excludeContainer 331 } 332 if !ctx.images[container.ImageID] { 333 return excludeContainer 334 } 335 } 336 337 return includeContainer 338 } 339 340 // transformContainer generates the container type expected by the docker ps command. 341 func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) { 342 newC := &types.Container{ 343 ID: container.ID, 344 Names: ctx.names[container.ID], 345 ImageID: container.ImageID.String(), 346 } 347 if newC.Names == nil { 348 // Dead containers will often have no name, so make sure the response isn't null 349 newC.Names = []string{} 350 } 351 352 image := container.Config.Image // if possible keep the original ref 353 if image != container.ImageID.String() { 354 id, err := daemon.GetImageID(image) 355 if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE { 356 return nil, err 357 } 358 if err != nil || id != container.ImageID { 359 image = container.ImageID.String() 360 } 361 } 362 newC.Image = image 363 364 if len(container.Args) > 0 { 365 args := []string{} 366 for _, arg := range container.Args { 367 if strings.Contains(arg, " ") { 368 args = append(args, fmt.Sprintf("'%s'", arg)) 369 } else { 370 args = append(args, arg) 371 } 372 } 373 argsAsString := strings.Join(args, " ") 374 375 newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) 376 } else { 377 newC.Command = container.Path 378 } 379 newC.Created = container.Created.Unix() 380 newC.Status = container.State.String() 381 newC.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode) 382 // copy networks to avoid races 383 networks := make(map[string]*networktypes.EndpointSettings) 384 for name, network := range container.NetworkSettings.Networks { 385 if network == nil { 386 continue 387 } 388 networks[name] = &networktypes.EndpointSettings{ 389 EndpointID: network.EndpointID, 390 Gateway: network.Gateway, 391 IPAddress: network.IPAddress, 392 IPPrefixLen: network.IPPrefixLen, 393 IPv6Gateway: network.IPv6Gateway, 394 GlobalIPv6Address: network.GlobalIPv6Address, 395 GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen, 396 MacAddress: network.MacAddress, 397 } 398 if network.IPAMConfig != nil { 399 networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{ 400 IPv4Address: network.IPAMConfig.IPv4Address, 401 IPv6Address: network.IPAMConfig.IPv6Address, 402 } 403 } 404 } 405 newC.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks} 406 407 newC.Ports = []types.Port{} 408 for port, bindings := range container.NetworkSettings.Ports { 409 p, err := nat.ParsePort(port.Port()) 410 if err != nil { 411 return nil, err 412 } 413 if len(bindings) == 0 { 414 newC.Ports = append(newC.Ports, types.Port{ 415 PrivatePort: p, 416 Type: port.Proto(), 417 }) 418 continue 419 } 420 for _, binding := range bindings { 421 h, err := nat.ParsePort(binding.HostPort) 422 if err != nil { 423 return nil, err 424 } 425 newC.Ports = append(newC.Ports, types.Port{ 426 PrivatePort: p, 427 PublicPort: h, 428 Type: port.Proto(), 429 IP: binding.HostIP, 430 }) 431 } 432 } 433 434 if ctx.Size { 435 sizeRw, sizeRootFs := daemon.getSize(container) 436 newC.SizeRw = sizeRw 437 newC.SizeRootFs = sizeRootFs 438 } 439 newC.Labels = container.Config.Labels 440 441 return newC, nil 442 } 443 444 // Volumes lists known volumes, using the filter to restrict the range 445 // of volumes returned. 446 func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, []string, error) { 447 var ( 448 volumesOut []*types.Volume 449 danglingOnly = false 450 ) 451 volFilters, err := filters.FromParam(filter) 452 if err != nil { 453 return nil, nil, err 454 } 455 456 if err := volFilters.Validate(acceptedVolumeFilterTags); err != nil { 457 return nil, nil, err 458 } 459 460 if volFilters.Include("dangling") { 461 if volFilters.ExactMatch("dangling", "true") || volFilters.ExactMatch("dangling", "1") { 462 danglingOnly = true 463 } else if !volFilters.ExactMatch("dangling", "false") && !volFilters.ExactMatch("dangling", "0") { 464 return nil, nil, fmt.Errorf("Invalid filter 'dangling=%s'", volFilters.Get("dangling")) 465 } 466 } 467 468 volumes, warnings, err := daemon.volumes.List() 469 if err != nil { 470 return nil, nil, err 471 } 472 if volFilters.Include("dangling") { 473 volumes = daemon.volumes.FilterByUsed(volumes, !danglingOnly) 474 } 475 for _, v := range volumes { 476 volumesOut = append(volumesOut, volumeToAPIType(v)) 477 } 478 return volumesOut, warnings, nil 479 } 480 481 func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) { 482 if !ancestorMap[imageID] { 483 for _, id := range getChildren(imageID) { 484 populateImageFilterByParents(ancestorMap, id, getChildren) 485 } 486 ancestorMap[imageID] = true 487 } 488 }