github.com/rawahars/moby@v24.0.4+incompatible/daemon/prune.go (about)

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