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