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  }