github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/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/api/types" 11 "github.com/docker/docker/image" 12 "github.com/docker/docker/pkg/graphdb" 13 "github.com/docker/docker/pkg/nat" 14 "github.com/docker/docker/pkg/parsers/filters" 15 ) 16 17 // iterationAction represents possible outcomes happening during the container iteration. 18 type iterationAction int 19 20 // containerReducer represents a reducer for a container. 21 // Returns the object to serialize by the api. 22 type containerReducer func(*Container, *listContext) (*types.Container, error) 23 24 const ( 25 // includeContainer is the action to include a container in the reducer. 26 includeContainer iterationAction = iota 27 // excludeContainer is the action to exclude a container in the reducer. 28 excludeContainer 29 // stopIteration is the action to stop iterating over the list of containers. 30 stopIteration 31 ) 32 33 // errStopIteration makes the iterator to stop without returning an error. 34 var errStopIteration = errors.New("container list iteration stopped") 35 36 // List returns an array of all containers registered in the daemon. 37 func (daemon *Daemon) List() []*Container { 38 return daemon.containers.List() 39 } 40 41 // ContainersConfig is the filtering specified by the user to iterate over containers. 42 type ContainersConfig struct { 43 // if true show all containers, otherwise only running containers. 44 All bool 45 // show all containers created after this container id 46 Since string 47 // show all containers created before this container id 48 Before string 49 // number of containers to return at most 50 Limit int 51 // if true include the sizes of the containers 52 Size bool 53 // return only containers that match filters 54 Filters string 55 } 56 57 // listContext is the daemon generated filtering to iterate over containers. 58 // This is created based on the user specification. 59 type listContext struct { 60 // idx is the container iteration index for this context 61 idx int 62 // ancestorFilter tells whether it should check ancestors or not 63 ancestorFilter bool 64 // names is a list of container names to filter with 65 names map[string][]string 66 // images is a list of images to filter with 67 images map[string]bool 68 // filters is a collection of arguments to filter with, specified by the user 69 filters filters.Args 70 // exitAllowed is a list of exit codes allowed to filter with 71 exitAllowed []int 72 // beforeContainer is a filter to ignore containers that appear before the one given 73 beforeContainer *Container 74 // sinceContainer is a filter to stop the filtering when the iterator arrive to the given container 75 sinceContainer *Container 76 // ContainersConfig is the filters set by the user 77 *ContainersConfig 78 } 79 80 // Containers returns the list of containers to show given the user's filtering. 81 func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, error) { 82 return daemon.reduceContainers(config, daemon.transformContainer) 83 } 84 85 // reduceContainer parses the user filtering and generates the list of containers to return based on a reducer. 86 func (daemon *Daemon) reduceContainers(config *ContainersConfig, reducer containerReducer) ([]*types.Container, error) { 87 containers := []*types.Container{} 88 89 ctx, err := daemon.foldFilter(config) 90 if err != nil { 91 return nil, err 92 } 93 94 for _, container := range daemon.List() { 95 t, err := daemon.reducePsContainer(container, ctx, reducer) 96 if err != nil { 97 if err != errStopIteration { 98 return nil, err 99 } 100 break 101 } 102 if t != nil { 103 containers = append(containers, t) 104 ctx.idx++ 105 } 106 } 107 return containers, nil 108 } 109 110 // reducePsContainer is the basic representation for a container as expected by the ps command. 111 func (daemon *Daemon) reducePsContainer(container *Container, ctx *listContext, reducer containerReducer) (*types.Container, error) { 112 container.Lock() 113 defer container.Unlock() 114 115 // filter containers to return 116 action := includeContainerInList(container, ctx) 117 switch action { 118 case excludeContainer: 119 return nil, nil 120 case stopIteration: 121 return nil, errStopIteration 122 } 123 124 // transform internal container struct into api structs 125 return reducer(container, ctx) 126 } 127 128 // foldFilter generates the container filter based in the user's filtering options. 129 func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) { 130 psFilters, err := filters.FromParam(config.Filters) 131 if err != nil { 132 return nil, err 133 } 134 135 var filtExited []int 136 if i, ok := psFilters["exited"]; ok { 137 for _, value := range i { 138 code, err := strconv.Atoi(value) 139 if err != nil { 140 return nil, err 141 } 142 filtExited = append(filtExited, code) 143 } 144 } 145 146 if i, ok := psFilters["status"]; ok { 147 for _, value := range i { 148 if !isValidStateString(value) { 149 return nil, errors.New("Unrecognised filter value for status") 150 } 151 if value == "exited" || value == "created" { 152 config.All = true 153 } 154 } 155 } 156 157 imagesFilter := map[string]bool{} 158 var ancestorFilter bool 159 if ancestors, ok := psFilters["ancestor"]; ok { 160 ancestorFilter = true 161 byParents := daemon.Graph().ByParent() 162 // The idea is to walk the graph down the most "efficient" way. 163 for _, ancestor := range ancestors { 164 // First, get the imageId of the ancestor filter (yay) 165 image, err := daemon.Repositories().LookupImage(ancestor) 166 if err != nil { 167 logrus.Warnf("Error while looking up for image %v", ancestor) 168 continue 169 } 170 if imagesFilter[ancestor] { 171 // Already seen this ancestor, skip it 172 continue 173 } 174 // Then walk down the graph and put the imageIds in imagesFilter 175 populateImageFilterByParents(imagesFilter, image.ID, byParents) 176 } 177 } 178 179 names := map[string][]string{} 180 daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error { 181 names[e.ID()] = append(names[e.ID()], p) 182 return nil 183 }, 1) 184 185 var beforeCont, sinceCont *Container 186 if config.Before != "" { 187 beforeCont, err = daemon.Get(config.Before) 188 if err != nil { 189 return nil, err 190 } 191 } 192 193 if config.Since != "" { 194 sinceCont, err = daemon.Get(config.Since) 195 if err != nil { 196 return nil, err 197 } 198 } 199 200 return &listContext{ 201 filters: psFilters, 202 ancestorFilter: ancestorFilter, 203 names: names, 204 images: imagesFilter, 205 exitAllowed: filtExited, 206 beforeContainer: beforeCont, 207 sinceContainer: sinceCont, 208 ContainersConfig: config, 209 }, nil 210 } 211 212 // includeContainerInList decides whether a containers should be include in the output or not based in the filter. 213 // It also decides if the iteration should be stopped or not. 214 func includeContainerInList(container *Container, ctx *listContext) iterationAction { 215 // Do not include container if it's stopped and we're not filters 216 if !container.Running && !ctx.All && ctx.Limit <= 0 && ctx.beforeContainer == nil && ctx.sinceContainer == nil { 217 return excludeContainer 218 } 219 220 // Do not include container if the name doesn't match 221 if !ctx.filters.Match("name", container.Name) { 222 return excludeContainer 223 } 224 225 // Do not include container if the id doesn't match 226 if !ctx.filters.Match("id", container.ID) { 227 return excludeContainer 228 } 229 230 // Do not include container if any of the labels don't match 231 if !ctx.filters.MatchKVList("label", container.Config.Labels) { 232 return excludeContainer 233 } 234 235 // Do not include container if it's in the list before the filter container. 236 // Set the filter container to nil to include the rest of containers after this one. 237 if ctx.beforeContainer != nil { 238 if container.ID == ctx.beforeContainer.ID { 239 ctx.beforeContainer = nil 240 } 241 return excludeContainer 242 } 243 244 // Stop iteration when the index is over the limit 245 if ctx.Limit > 0 && ctx.idx == ctx.Limit { 246 return stopIteration 247 } 248 249 // Stop interation when the container arrives to the filter container 250 if ctx.sinceContainer != nil { 251 if container.ID == ctx.sinceContainer.ID { 252 return stopIteration 253 } 254 } 255 256 // Do not include container if its exit code is not in the filter 257 if len(ctx.exitAllowed) > 0 { 258 shouldSkip := true 259 for _, code := range ctx.exitAllowed { 260 if code == container.ExitCode && !container.Running { 261 shouldSkip = false 262 break 263 } 264 } 265 if shouldSkip { 266 return excludeContainer 267 } 268 } 269 270 // Do not include container if its status doesn't match the filter 271 if !ctx.filters.Match("status", container.State.StateString()) { 272 return excludeContainer 273 } 274 275 if ctx.ancestorFilter { 276 if len(ctx.images) == 0 { 277 return excludeContainer 278 } 279 if !ctx.images[container.ImageID] { 280 return excludeContainer 281 } 282 } 283 284 return includeContainer 285 } 286 287 // transformContainer generates the container type expected by the docker ps command. 288 func (daemon *Daemon) transformContainer(container *Container, ctx *listContext) (*types.Container, error) { 289 newC := &types.Container{ 290 ID: container.ID, 291 Names: ctx.names[container.ID], 292 } 293 294 img, err := daemon.Repositories().LookupImage(container.Config.Image) 295 if err != nil { 296 // If the image can no longer be found by its original reference, 297 // it makes sense to show the ID instead of a stale reference. 298 newC.Image = container.ImageID 299 } else if container.ImageID == img.ID { 300 newC.Image = container.Config.Image 301 } else { 302 newC.Image = container.ImageID 303 } 304 305 if len(container.Args) > 0 { 306 args := []string{} 307 for _, arg := range container.Args { 308 if strings.Contains(arg, " ") { 309 args = append(args, fmt.Sprintf("'%s'", arg)) 310 } else { 311 args = append(args, arg) 312 } 313 } 314 argsAsString := strings.Join(args, " ") 315 316 newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) 317 } else { 318 newC.Command = container.Path 319 } 320 newC.Created = container.Created.Unix() 321 newC.Status = container.State.String() 322 newC.HostConfig.NetworkMode = string(container.hostConfig.NetworkMode) 323 324 newC.Ports = []types.Port{} 325 for port, bindings := range container.NetworkSettings.Ports { 326 p, err := nat.ParsePort(port.Port()) 327 if err != nil { 328 return nil, err 329 } 330 if len(bindings) == 0 { 331 newC.Ports = append(newC.Ports, types.Port{ 332 PrivatePort: p, 333 Type: port.Proto(), 334 }) 335 continue 336 } 337 for _, binding := range bindings { 338 h, err := nat.ParsePort(binding.HostPort) 339 if err != nil { 340 return nil, err 341 } 342 newC.Ports = append(newC.Ports, types.Port{ 343 PrivatePort: p, 344 PublicPort: h, 345 Type: port.Proto(), 346 IP: binding.HostIP, 347 }) 348 } 349 } 350 351 if ctx.Size { 352 sizeRw, sizeRootFs := container.getSize() 353 newC.SizeRw = sizeRw 354 newC.SizeRootFs = sizeRootFs 355 } 356 newC.Labels = container.Config.Labels 357 358 return newC, nil 359 } 360 361 // Volumes lists known volumes, using the filter to restrict the range 362 // of volumes returned. 363 func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) { 364 var volumesOut []*types.Volume 365 volFilters, err := filters.FromParam(filter) 366 if err != nil { 367 return nil, err 368 } 369 370 filterUsed := false 371 if i, ok := volFilters["dangling"]; ok { 372 if len(i) > 1 { 373 return nil, fmt.Errorf("Conflict: cannot use more than 1 value for `dangling` filter") 374 } 375 376 filterValue := i[0] 377 if strings.ToLower(filterValue) == "true" || filterValue == "1" { 378 filterUsed = true 379 } 380 } 381 382 volumes := daemon.volumes.List() 383 for _, v := range volumes { 384 if filterUsed && daemon.volumes.Count(v) > 0 { 385 continue 386 } 387 volumesOut = append(volumesOut, volumeToAPIType(v)) 388 } 389 return volumesOut, nil 390 } 391 392 func populateImageFilterByParents(ancestorMap map[string]bool, imageID string, byParents map[string][]*image.Image) { 393 if !ancestorMap[imageID] { 394 if images, ok := byParents[imageID]; ok { 395 for _, image := range images { 396 populateImageFilterByParents(ancestorMap, image.ID, byParents) 397 } 398 } 399 ancestorMap[imageID] = true 400 } 401 }