github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/daemon/prune.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "regexp" 6 "sync/atomic" 7 "time" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/distribution/reference" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/filters" 13 timetypes "github.com/docker/docker/api/types/time" 14 "github.com/docker/docker/image" 15 "github.com/docker/docker/layer" 16 "github.com/docker/docker/pkg/directory" 17 "github.com/docker/docker/runconfig" 18 "github.com/docker/docker/volume" 19 "github.com/docker/libnetwork" 20 digest "github.com/opencontainers/go-digest" 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.Warnf("ContainersPrune operation cancelled: %#v", *rep) 76 return rep, ctx.Err() 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.Warnf("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.volumes.Remove(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 155 return rep, err 156 } 157 158 // ImagesPrune removes unused images 159 func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) { 160 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 161 return nil, errPruneRunning 162 } 163 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 164 165 // make sure that only accepted filters have been received 166 err := pruneFilters.Validate(imagesAcceptedFilters) 167 if err != nil { 168 return nil, err 169 } 170 171 rep := &types.ImagesPruneReport{} 172 173 danglingOnly := true 174 if pruneFilters.Include("dangling") { 175 if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") { 176 danglingOnly = false 177 } else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") { 178 return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling")) 179 } 180 } 181 182 until, err := getUntilFromPruneFilters(pruneFilters) 183 if err != nil { 184 return nil, err 185 } 186 187 var allImages map[image.ID]*image.Image 188 if danglingOnly { 189 allImages = daemon.imageStore.Heads() 190 } else { 191 allImages = daemon.imageStore.Map() 192 } 193 allContainers := daemon.List() 194 imageRefs := map[string]bool{} 195 for _, c := range allContainers { 196 select { 197 case <-ctx.Done(): 198 return nil, ctx.Err() 199 default: 200 imageRefs[c.ID] = true 201 } 202 } 203 204 // Filter intermediary images and get their unique size 205 allLayers := daemon.layerStore.Map() 206 topImages := map[image.ID]*image.Image{} 207 for id, img := range allImages { 208 select { 209 case <-ctx.Done(): 210 return nil, ctx.Err() 211 default: 212 dgst := digest.Digest(id) 213 if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 { 214 continue 215 } 216 if !until.IsZero() && img.Created.After(until) { 217 continue 218 } 219 if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) { 220 continue 221 } 222 topImages[id] = img 223 } 224 } 225 226 canceled := false 227 deleteImagesLoop: 228 for id := range topImages { 229 select { 230 case <-ctx.Done(): 231 // we still want to calculate freed size and return the data 232 canceled = true 233 break deleteImagesLoop 234 default: 235 } 236 237 dgst := digest.Digest(id) 238 hex := dgst.Hex() 239 if _, ok := imageRefs[hex]; ok { 240 continue 241 } 242 243 deletedImages := []types.ImageDeleteResponseItem{} 244 refs := daemon.referenceStore.References(dgst) 245 if len(refs) > 0 { 246 shouldDelete := !danglingOnly 247 if !shouldDelete { 248 hasTag := false 249 for _, ref := range refs { 250 if _, ok := ref.(reference.NamedTagged); ok { 251 hasTag = true 252 break 253 } 254 } 255 256 // Only delete if it's untagged (i.e. repo:<none>) 257 shouldDelete = !hasTag 258 } 259 260 if shouldDelete { 261 for _, ref := range refs { 262 imgDel, err := daemon.ImageDelete(ref.String(), false, true) 263 if err != nil { 264 logrus.Warnf("could not delete reference %s: %v", ref.String(), err) 265 continue 266 } 267 deletedImages = append(deletedImages, imgDel...) 268 } 269 } 270 } else { 271 imgDel, err := daemon.ImageDelete(hex, false, true) 272 if err != nil { 273 logrus.Warnf("could not delete image %s: %v", hex, err) 274 continue 275 } 276 deletedImages = append(deletedImages, imgDel...) 277 } 278 279 rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...) 280 } 281 282 // Compute how much space was freed 283 for _, d := range rep.ImagesDeleted { 284 if d.Deleted != "" { 285 chid := layer.ChainID(d.Deleted) 286 if l, ok := allLayers[chid]; ok { 287 diffSize, err := l.DiffSize() 288 if err != nil { 289 logrus.Warnf("failed to get layer %s size: %v", chid, err) 290 continue 291 } 292 rep.SpaceReclaimed += uint64(diffSize) 293 } 294 } 295 } 296 297 if canceled { 298 logrus.Warnf("ImagesPrune operation cancelled: %#v", *rep) 299 return nil, ctx.Err() 300 } 301 302 return rep, nil 303 } 304 305 // localNetworksPrune removes unused local networks 306 func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport { 307 rep := &types.NetworksPruneReport{} 308 309 until, _ := getUntilFromPruneFilters(pruneFilters) 310 311 // When the function returns true, the walk will stop. 312 l := func(nw libnetwork.Network) bool { 313 select { 314 case <-ctx.Done(): 315 return true 316 default: 317 } 318 if nw.Info().ConfigOnly() { 319 return false 320 } 321 if !until.IsZero() && nw.Info().Created().After(until) { 322 return false 323 } 324 if !matchLabels(pruneFilters, nw.Info().Labels()) { 325 return false 326 } 327 nwName := nw.Name() 328 if runconfig.IsPreDefinedNetwork(nwName) { 329 return false 330 } 331 if len(nw.Endpoints()) > 0 { 332 return false 333 } 334 if err := daemon.DeleteNetwork(nw.ID()); err != nil { 335 logrus.Warnf("could not remove local network %s: %v", nwName, err) 336 return false 337 } 338 rep.NetworksDeleted = append(rep.NetworksDeleted, nwName) 339 return false 340 } 341 daemon.netController.WalkNetworks(l) 342 return rep 343 } 344 345 // clusterNetworksPrune removes unused cluster networks 346 func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { 347 rep := &types.NetworksPruneReport{} 348 349 until, _ := getUntilFromPruneFilters(pruneFilters) 350 351 cluster := daemon.GetCluster() 352 353 if !cluster.IsManager() { 354 return rep, nil 355 } 356 357 networks, err := cluster.GetNetworks() 358 if err != nil { 359 return rep, err 360 } 361 networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`) 362 for _, nw := range networks { 363 select { 364 case <-ctx.Done(): 365 return rep, ctx.Err() 366 default: 367 if nw.Ingress { 368 // Routing-mesh network removal has to be explicitly invoked by user 369 continue 370 } 371 if !until.IsZero() && nw.Created.After(until) { 372 continue 373 } 374 if !matchLabels(pruneFilters, nw.Labels) { 375 continue 376 } 377 // https://github.com/docker/docker/issues/24186 378 // `docker network inspect` unfortunately displays ONLY those containers that are local to that node. 379 // So we try to remove it anyway and check the error 380 err = cluster.RemoveNetwork(nw.ID) 381 if err != nil { 382 // we can safely ignore the "network .. is in use" error 383 match := networkIsInUse.FindStringSubmatch(err.Error()) 384 if len(match) != 2 || match[1] != nw.ID { 385 logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err) 386 } 387 continue 388 } 389 rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name) 390 } 391 } 392 return rep, nil 393 } 394 395 // NetworksPrune removes unused networks 396 func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { 397 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 398 return nil, errPruneRunning 399 } 400 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 401 402 // make sure that only accepted filters have been received 403 err := pruneFilters.Validate(networksAcceptedFilters) 404 if err != nil { 405 return nil, err 406 } 407 408 if _, err := getUntilFromPruneFilters(pruneFilters); err != nil { 409 return nil, err 410 } 411 412 rep := &types.NetworksPruneReport{} 413 if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil { 414 rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...) 415 } 416 417 localRep := daemon.localNetworksPrune(ctx, pruneFilters) 418 rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...) 419 420 select { 421 case <-ctx.Done(): 422 logrus.Warnf("NetworksPrune operation cancelled: %#v", *rep) 423 return nil, ctx.Err() 424 default: 425 } 426 427 return rep, nil 428 } 429 430 func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { 431 until := time.Time{} 432 if !pruneFilters.Include("until") { 433 return until, nil 434 } 435 untilFilters := pruneFilters.Get("until") 436 if len(untilFilters) > 1 { 437 return until, fmt.Errorf("more than one until filter specified") 438 } 439 ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now()) 440 if err != nil { 441 return until, err 442 } 443 seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) 444 if err != nil { 445 return until, err 446 } 447 until = time.Unix(seconds, nanoseconds) 448 return until, nil 449 } 450 451 func matchLabels(pruneFilters filters.Args, labels map[string]string) bool { 452 if !pruneFilters.MatchKVList("label", labels) { 453 return false 454 } 455 // By default MatchKVList will return true if field (like 'label!') does not exist 456 // So we have to add additional Include("label!") check 457 if pruneFilters.Include("label!") { 458 if pruneFilters.MatchKVList("label!", labels) { 459 return false 460 } 461 } 462 return true 463 }