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