github.com/ssdev-go/moby@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  }