github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/prune.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"sync/atomic"
     8  	"time"
     9  
    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/errdefs"
    14  	"github.com/docker/docker/runconfig"
    15  	"github.com/docker/libnetwork"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  var (
    21  	// errPruneRunning is returned when a prune request is received while
    22  	// one is in progress
    23  	errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
    24  
    25  	containersAcceptedFilters = map[string]bool{
    26  		"label":  true,
    27  		"label!": true,
    28  		"until":  true,
    29  	}
    30  
    31  	networksAcceptedFilters = map[string]bool{
    32  		"label":  true,
    33  		"label!": true,
    34  		"until":  true,
    35  	}
    36  )
    37  
    38  // ContainersPrune removes unused containers
    39  func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
    40  	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
    41  		return nil, errPruneRunning
    42  	}
    43  	defer atomic.StoreInt32(&daemon.pruneRunning, 0)
    44  
    45  	rep := &types.ContainersPruneReport{}
    46  
    47  	// make sure that only accepted filters have been received
    48  	err := pruneFilters.Validate(containersAcceptedFilters)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	until, err := getUntilFromPruneFilters(pruneFilters)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	allContainers := daemon.List()
    59  	for _, c := range allContainers {
    60  		select {
    61  		case <-ctx.Done():
    62  			logrus.Debugf("ContainersPrune operation cancelled: %#v", *rep)
    63  			return rep, nil
    64  		default:
    65  		}
    66  
    67  		if !c.IsRunning() {
    68  			if !until.IsZero() && c.Created.After(until) {
    69  				continue
    70  			}
    71  			if !matchLabels(pruneFilters, c.Config.Labels) {
    72  				continue
    73  			}
    74  			cSize, _ := daemon.imageService.GetContainerLayerSize(c.ID)
    75  			// TODO: sets RmLink to true?
    76  			err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
    77  			if err != nil {
    78  				logrus.Warnf("failed to prune container %s: %v", c.ID, err)
    79  				continue
    80  			}
    81  			if cSize > 0 {
    82  				rep.SpaceReclaimed += uint64(cSize)
    83  			}
    84  			rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
    85  		}
    86  	}
    87  
    88  	return rep, nil
    89  }
    90  
    91  // localNetworksPrune removes unused local networks
    92  func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
    93  	rep := &types.NetworksPruneReport{}
    94  
    95  	until, _ := getUntilFromPruneFilters(pruneFilters)
    96  
    97  	// When the function returns true, the walk will stop.
    98  	l := func(nw libnetwork.Network) bool {
    99  		select {
   100  		case <-ctx.Done():
   101  			// context cancelled
   102  			return true
   103  		default:
   104  		}
   105  		if nw.Info().ConfigOnly() {
   106  			return false
   107  		}
   108  		if !until.IsZero() && nw.Info().Created().After(until) {
   109  			return false
   110  		}
   111  		if !matchLabels(pruneFilters, nw.Info().Labels()) {
   112  			return false
   113  		}
   114  		nwName := nw.Name()
   115  		if runconfig.IsPreDefinedNetwork(nwName) {
   116  			return false
   117  		}
   118  		if len(nw.Endpoints()) > 0 {
   119  			return false
   120  		}
   121  		if err := daemon.DeleteNetwork(nw.ID()); err != nil {
   122  			logrus.Warnf("could not remove local network %s: %v", nwName, err)
   123  			return false
   124  		}
   125  		rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
   126  		return false
   127  	}
   128  	daemon.netController.WalkNetworks(l)
   129  	return rep
   130  }
   131  
   132  // clusterNetworksPrune removes unused cluster networks
   133  func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
   134  	rep := &types.NetworksPruneReport{}
   135  
   136  	until, _ := getUntilFromPruneFilters(pruneFilters)
   137  
   138  	cluster := daemon.GetCluster()
   139  
   140  	if !cluster.IsManager() {
   141  		return rep, nil
   142  	}
   143  
   144  	networks, err := cluster.GetNetworks(pruneFilters)
   145  	if err != nil {
   146  		return rep, err
   147  	}
   148  	networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
   149  	for _, nw := range networks {
   150  		select {
   151  		case <-ctx.Done():
   152  			return rep, nil
   153  		default:
   154  			if nw.Ingress {
   155  				// Routing-mesh network removal has to be explicitly invoked by user
   156  				continue
   157  			}
   158  			if !until.IsZero() && nw.Created.After(until) {
   159  				continue
   160  			}
   161  			if !matchLabels(pruneFilters, nw.Labels) {
   162  				continue
   163  			}
   164  			// https://github.com/docker/docker/issues/24186
   165  			// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
   166  			// So we try to remove it anyway and check the error
   167  			err = cluster.RemoveNetwork(nw.ID)
   168  			if err != nil {
   169  				// we can safely ignore the "network .. is in use" error
   170  				match := networkIsInUse.FindStringSubmatch(err.Error())
   171  				if len(match) != 2 || match[1] != nw.ID {
   172  					logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
   173  				}
   174  				continue
   175  			}
   176  			rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
   177  		}
   178  	}
   179  	return rep, nil
   180  }
   181  
   182  // NetworksPrune removes unused networks
   183  func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
   184  	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
   185  		return nil, errPruneRunning
   186  	}
   187  	defer atomic.StoreInt32(&daemon.pruneRunning, 0)
   188  
   189  	// make sure that only accepted filters have been received
   190  	err := pruneFilters.Validate(networksAcceptedFilters)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	rep := &types.NetworksPruneReport{}
   200  	if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil {
   201  		rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
   202  	}
   203  
   204  	localRep := daemon.localNetworksPrune(ctx, pruneFilters)
   205  	rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
   206  
   207  	select {
   208  	case <-ctx.Done():
   209  		logrus.Debugf("NetworksPrune operation cancelled: %#v", *rep)
   210  		return rep, nil
   211  	default:
   212  	}
   213  
   214  	return rep, nil
   215  }
   216  
   217  func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
   218  	until := time.Time{}
   219  	if !pruneFilters.Contains("until") {
   220  		return until, nil
   221  	}
   222  	untilFilters := pruneFilters.Get("until")
   223  	if len(untilFilters) > 1 {
   224  		return until, fmt.Errorf("more than one until filter specified")
   225  	}
   226  	ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
   227  	if err != nil {
   228  		return until, err
   229  	}
   230  	seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
   231  	if err != nil {
   232  		return until, err
   233  	}
   234  	until = time.Unix(seconds, nanoseconds)
   235  	return until, nil
   236  }
   237  
   238  func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {
   239  	if !pruneFilters.MatchKVList("label", labels) {
   240  		return false
   241  	}
   242  	// By default MatchKVList will return true if field (like 'label!') does not exist
   243  	// So we have to add additional Contains("label!") check
   244  	if pruneFilters.Contains("label!") {
   245  		if pruneFilters.MatchKVList("label!", labels) {
   246  			return false
   247  		}
   248  	}
   249  	return true
   250  }