github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/daemon/prune.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "regexp" 6 "sync/atomic" 7 "time" 8 9 "github.com/docker/distribution/reference" 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/api/types/filters" 12 timetypes "github.com/docker/docker/api/types/time" 13 "github.com/docker/docker/image" 14 "github.com/docker/docker/layer" 15 "github.com/docker/docker/pkg/directory" 16 "github.com/docker/docker/runconfig" 17 "github.com/docker/docker/volume" 18 "github.com/docker/libnetwork" 19 digest "github.com/opencontainers/go-digest" 20 "github.com/sirupsen/logrus" 21 "golang.org/x/net/context" 22 ) 23 24 var ( 25 // errPruneRunning is returned when a prune request is received while 26 // one is in progress 27 errPruneRunning = fmt.Errorf("a prune operation is already running") 28 29 containersAcceptedFilters = map[string]bool{ 30 "label": true, 31 "label!": true, 32 "until": true, 33 } 34 volumesAcceptedFilters = map[string]bool{ 35 "label": true, 36 "label!": true, 37 } 38 imagesAcceptedFilters = map[string]bool{ 39 "dangling": true, 40 "label": true, 41 "label!": true, 42 "until": true, 43 } 44 networksAcceptedFilters = map[string]bool{ 45 "label": true, 46 "label!": true, 47 "until": true, 48 } 49 ) 50 51 // ContainersPrune removes unused containers 52 func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) { 53 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 54 return nil, errPruneRunning 55 } 56 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 57 58 rep := &types.ContainersPruneReport{} 59 60 // make sure that only accepted filters have been received 61 err := pruneFilters.Validate(containersAcceptedFilters) 62 if err != nil { 63 return nil, err 64 } 65 66 until, err := getUntilFromPruneFilters(pruneFilters) 67 if err != nil { 68 return nil, err 69 } 70 71 allContainers := daemon.List() 72 for _, c := range allContainers { 73 select { 74 case <-ctx.Done(): 75 logrus.Debugf("ContainersPrune operation cancelled: %#v", *rep) 76 return rep, nil 77 default: 78 } 79 80 if !c.IsRunning() { 81 if !until.IsZero() && c.Created.After(until) { 82 continue 83 } 84 if !matchLabels(pruneFilters, c.Config.Labels) { 85 continue 86 } 87 cSize, _ := daemon.getSize(c.ID) 88 // TODO: sets RmLink to true? 89 err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{}) 90 if err != nil { 91 logrus.Warnf("failed to prune container %s: %v", c.ID, err) 92 continue 93 } 94 if cSize > 0 { 95 rep.SpaceReclaimed += uint64(cSize) 96 } 97 rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID) 98 } 99 } 100 101 return rep, nil 102 } 103 104 // VolumesPrune removes unused local volumes 105 func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) { 106 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 107 return nil, errPruneRunning 108 } 109 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 110 111 // make sure that only accepted filters have been received 112 err := pruneFilters.Validate(volumesAcceptedFilters) 113 if err != nil { 114 return nil, err 115 } 116 117 rep := &types.VolumesPruneReport{} 118 119 pruneVols := func(v volume.Volume) error { 120 select { 121 case <-ctx.Done(): 122 logrus.Debugf("VolumesPrune operation cancelled: %#v", *rep) 123 return ctx.Err() 124 default: 125 } 126 127 name := v.Name() 128 refs := daemon.volumes.Refs(v) 129 130 if len(refs) == 0 { 131 detailedVolume, ok := v.(volume.DetailedVolume) 132 if ok { 133 if !matchLabels(pruneFilters, detailedVolume.Labels()) { 134 return nil 135 } 136 } 137 vSize, err := directory.Size(v.Path()) 138 if err != nil { 139 logrus.Warnf("could not determine size of volume %s: %v", name, err) 140 } 141 err = daemon.volumeRm(v) 142 if err != nil { 143 logrus.Warnf("could not remove volume %s: %v", name, err) 144 return nil 145 } 146 rep.SpaceReclaimed += uint64(vSize) 147 rep.VolumesDeleted = append(rep.VolumesDeleted, name) 148 } 149 150 return nil 151 } 152 153 err = daemon.traverseLocalVolumes(pruneVols) 154 if err == context.Canceled { 155 return rep, nil 156 } 157 158 return rep, err 159 } 160 161 // ImagesPrune removes unused images 162 func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) { 163 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 164 return nil, errPruneRunning 165 } 166 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 167 168 // make sure that only accepted filters have been received 169 err := pruneFilters.Validate(imagesAcceptedFilters) 170 if err != nil { 171 return nil, err 172 } 173 174 rep := &types.ImagesPruneReport{} 175 176 danglingOnly := true 177 if pruneFilters.Contains("dangling") { 178 if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") { 179 danglingOnly = false 180 } else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") { 181 return nil, invalidFilter{"dangling", pruneFilters.Get("dangling")} 182 } 183 } 184 185 until, err := getUntilFromPruneFilters(pruneFilters) 186 if err != nil { 187 return nil, err 188 } 189 190 var allImages map[image.ID]*image.Image 191 if danglingOnly { 192 allImages = daemon.imageStore.Heads() 193 } else { 194 allImages = daemon.imageStore.Map() 195 } 196 allContainers := daemon.List() 197 imageRefs := map[string]bool{} 198 for _, c := range allContainers { 199 select { 200 case <-ctx.Done(): 201 return nil, ctx.Err() 202 default: 203 imageRefs[c.ID] = true 204 } 205 } 206 207 // Filter intermediary images and get their unique size 208 allLayers := make(map[layer.ChainID]layer.Layer) 209 for _, ls := range daemon.layerStores { 210 for k, v := range ls.Map() { 211 allLayers[k] = v 212 } 213 } 214 topImages := map[image.ID]*image.Image{} 215 for id, img := range allImages { 216 select { 217 case <-ctx.Done(): 218 return nil, ctx.Err() 219 default: 220 dgst := digest.Digest(id) 221 if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 { 222 continue 223 } 224 if !until.IsZero() && img.Created.After(until) { 225 continue 226 } 227 if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) { 228 continue 229 } 230 topImages[id] = img 231 } 232 } 233 234 canceled := false 235 deleteImagesLoop: 236 for id := range topImages { 237 select { 238 case <-ctx.Done(): 239 // we still want to calculate freed size and return the data 240 canceled = true 241 break deleteImagesLoop 242 default: 243 } 244 245 dgst := digest.Digest(id) 246 hex := dgst.Hex() 247 if _, ok := imageRefs[hex]; ok { 248 continue 249 } 250 251 deletedImages := []types.ImageDeleteResponseItem{} 252 refs := daemon.referenceStore.References(dgst) 253 if len(refs) > 0 { 254 shouldDelete := !danglingOnly 255 if !shouldDelete { 256 hasTag := false 257 for _, ref := range refs { 258 if _, ok := ref.(reference.NamedTagged); ok { 259 hasTag = true 260 break 261 } 262 } 263 264 // Only delete if it's untagged (i.e. repo:<none>) 265 shouldDelete = !hasTag 266 } 267 268 if shouldDelete { 269 for _, ref := range refs { 270 imgDel, err := daemon.ImageDelete(ref.String(), false, true) 271 if err != nil { 272 logrus.Warnf("could not delete reference %s: %v", ref.String(), err) 273 continue 274 } 275 deletedImages = append(deletedImages, imgDel...) 276 } 277 } 278 } else { 279 imgDel, err := daemon.ImageDelete(hex, false, true) 280 if err != nil { 281 logrus.Warnf("could not delete image %s: %v", hex, err) 282 continue 283 } 284 deletedImages = append(deletedImages, imgDel...) 285 } 286 287 rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...) 288 } 289 290 // Compute how much space was freed 291 for _, d := range rep.ImagesDeleted { 292 if d.Deleted != "" { 293 chid := layer.ChainID(d.Deleted) 294 if l, ok := allLayers[chid]; ok { 295 diffSize, err := l.DiffSize() 296 if err != nil { 297 logrus.Warnf("failed to get layer %s size: %v", chid, err) 298 continue 299 } 300 rep.SpaceReclaimed += uint64(diffSize) 301 } 302 } 303 } 304 305 if canceled { 306 logrus.Debugf("ImagesPrune operation cancelled: %#v", *rep) 307 } 308 309 return rep, nil 310 } 311 312 // localNetworksPrune removes unused local networks 313 func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport { 314 rep := &types.NetworksPruneReport{} 315 316 until, _ := getUntilFromPruneFilters(pruneFilters) 317 318 // When the function returns true, the walk will stop. 319 l := func(nw libnetwork.Network) bool { 320 select { 321 case <-ctx.Done(): 322 // context cancelled 323 return true 324 default: 325 } 326 if nw.Info().ConfigOnly() { 327 return false 328 } 329 if !until.IsZero() && nw.Info().Created().After(until) { 330 return false 331 } 332 if !matchLabels(pruneFilters, nw.Info().Labels()) { 333 return false 334 } 335 nwName := nw.Name() 336 if runconfig.IsPreDefinedNetwork(nwName) { 337 return false 338 } 339 if len(nw.Endpoints()) > 0 { 340 return false 341 } 342 if err := daemon.DeleteNetwork(nw.ID()); err != nil { 343 logrus.Warnf("could not remove local network %s: %v", nwName, err) 344 return false 345 } 346 rep.NetworksDeleted = append(rep.NetworksDeleted, nwName) 347 return false 348 } 349 daemon.netController.WalkNetworks(l) 350 return rep 351 } 352 353 // clusterNetworksPrune removes unused cluster networks 354 func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { 355 rep := &types.NetworksPruneReport{} 356 357 until, _ := getUntilFromPruneFilters(pruneFilters) 358 359 cluster := daemon.GetCluster() 360 361 if !cluster.IsManager() { 362 return rep, nil 363 } 364 365 networks, err := cluster.GetNetworks() 366 if err != nil { 367 return rep, err 368 } 369 networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`) 370 for _, nw := range networks { 371 select { 372 case <-ctx.Done(): 373 return rep, nil 374 default: 375 if nw.Ingress { 376 // Routing-mesh network removal has to be explicitly invoked by user 377 continue 378 } 379 if !until.IsZero() && nw.Created.After(until) { 380 continue 381 } 382 if !matchLabels(pruneFilters, nw.Labels) { 383 continue 384 } 385 // https://github.com/docker/docker/issues/24186 386 // `docker network inspect` unfortunately displays ONLY those containers that are local to that node. 387 // So we try to remove it anyway and check the error 388 err = cluster.RemoveNetwork(nw.ID) 389 if err != nil { 390 // we can safely ignore the "network .. is in use" error 391 match := networkIsInUse.FindStringSubmatch(err.Error()) 392 if len(match) != 2 || match[1] != nw.ID { 393 logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err) 394 } 395 continue 396 } 397 rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name) 398 } 399 } 400 return rep, nil 401 } 402 403 // NetworksPrune removes unused networks 404 func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { 405 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 406 return nil, errPruneRunning 407 } 408 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 409 410 // make sure that only accepted filters have been received 411 err := pruneFilters.Validate(networksAcceptedFilters) 412 if err != nil { 413 return nil, err 414 } 415 416 if _, err := getUntilFromPruneFilters(pruneFilters); err != nil { 417 return nil, err 418 } 419 420 rep := &types.NetworksPruneReport{} 421 if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil { 422 rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...) 423 } 424 425 localRep := daemon.localNetworksPrune(ctx, pruneFilters) 426 rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...) 427 428 select { 429 case <-ctx.Done(): 430 logrus.Debugf("NetworksPrune operation cancelled: %#v", *rep) 431 return rep, nil 432 default: 433 } 434 435 return rep, nil 436 } 437 438 func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { 439 until := time.Time{} 440 if !pruneFilters.Contains("until") { 441 return until, nil 442 } 443 untilFilters := pruneFilters.Get("until") 444 if len(untilFilters) > 1 { 445 return until, fmt.Errorf("more than one until filter specified") 446 } 447 ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now()) 448 if err != nil { 449 return until, err 450 } 451 seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) 452 if err != nil { 453 return until, err 454 } 455 until = time.Unix(seconds, nanoseconds) 456 return until, nil 457 } 458 459 func matchLabels(pruneFilters filters.Args, labels map[string]string) bool { 460 if !pruneFilters.MatchKVList("label", labels) { 461 return false 462 } 463 // By default MatchKVList will return true if field (like 'label!') does not exist 464 // So we have to add additional Contains("label!") check 465 if pruneFilters.Contains("label!") { 466 if pruneFilters.MatchKVList("label!", labels) { 467 return false 468 } 469 } 470 return true 471 }