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  }