daemon/prune.go
4f0d95fa
 package daemon // import "github.com/docker/docker/daemon"
33f4d68f
 
 import (
7d62e40f
 	"context"
a6be56b5
 	"fmt"
7e24c160
 	"regexp"
3279ca3c
 	"sync/atomic"
58738cde
 	"time"
7e24c160
 
33f4d68f
 	"github.com/docker/docker/api/types"
a6be56b5
 	"github.com/docker/docker/api/types/filters"
58738cde
 	timetypes "github.com/docker/docker/api/types/time"
e4b6adc8
 	"github.com/docker/docker/errdefs"
7e24c160
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/libnetwork"
e4b6adc8
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
3279ca3c
 )
 
 var (
2a8f46ab
 	// errPruneRunning is returned when a prune request is received while
3279ca3c
 	// one is in progress
e4b6adc8
 	errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
71760ae6
 
 	containersAcceptedFilters = map[string]bool{
 		"label":  true,
 		"label!": true,
 		"until":  true,
 	}
9c25df0f
 
71760ae6
 	networksAcceptedFilters = map[string]bool{
 		"label":  true,
 		"label!": true,
 		"until":  true,
 	}
33f4d68f
 )
 
fd62b6c9
 // ContainersPrune removes unused containers
0dee6979
 func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
3279ca3c
 	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
2a8f46ab
 		return nil, errPruneRunning
3279ca3c
 	}
 	defer atomic.StoreInt32(&daemon.pruneRunning, 0)
 
33f4d68f
 	rep := &types.ContainersPruneReport{}
 
71760ae6
 	// make sure that only accepted filters have been received
 	err := pruneFilters.Validate(containersAcceptedFilters)
 	if err != nil {
 		return nil, err
 	}
 
58738cde
 	until, err := getUntilFromPruneFilters(pruneFilters)
 	if err != nil {
 		return nil, err
 	}
 
33f4d68f
 	allContainers := daemon.List()
 	for _, c := range allContainers {
0dee6979
 		select {
 		case <-ctx.Done():
87b4dc20
 			logrus.Debugf("ContainersPrune operation cancelled: %#v", *rep)
 			return rep, nil
0dee6979
 		default:
 		}
 
33f4d68f
 		if !c.IsRunning() {
58738cde
 			if !until.IsZero() && c.Created.After(until) {
 				continue
 			}
70252473
 			if !matchLabels(pruneFilters, c.Config.Labels) {
 				continue
 			}
0dab53ff
 			cSize, _ := daemon.imageService.GetContainerLayerSize(c.ID)
33f4d68f
 			// TODO: sets RmLink to true?
 			err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
 			if err != nil {
fd62b6c9
 				logrus.Warnf("failed to prune container %s: %v", c.ID, err)
33f4d68f
 				continue
 			}
 			if cSize > 0 {
 				rep.SpaceReclaimed += uint64(cSize)
 			}
 			rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
 		}
 	}
 
 	return rep, nil
 }
 
7e24c160
 // localNetworksPrune removes unused local networks
0dee6979
 func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
7e24c160
 	rep := &types.NetworksPruneReport{}
58738cde
 
06c4c5f4
 	until, _ := getUntilFromPruneFilters(pruneFilters)
58738cde
 
7e24c160
 	// When the function returns true, the walk will stop.
 	l := func(nw libnetwork.Network) bool {
0dee6979
 		select {
 		case <-ctx.Done():
87b4dc20
 			// context cancelled
0dee6979
 			return true
 		default:
 		}
9ee7b4dd
 		if nw.Info().ConfigOnly() {
 			return false
 		}
58738cde
 		if !until.IsZero() && nw.Info().Created().After(until) {
 			return false
 		}
70252473
 		if !matchLabels(pruneFilters, nw.Info().Labels()) {
 			return false
 		}
7e24c160
 		nwName := nw.Name()
06c4c5f4
 		if runconfig.IsPreDefinedNetwork(nwName) {
 			return false
 		}
 		if len(nw.Endpoints()) > 0 {
 			return false
 		}
 		if err := daemon.DeleteNetwork(nw.ID()); err != nil {
 			logrus.Warnf("could not remove local network %s: %v", nwName, err)
 			return false
7e24c160
 		}
06c4c5f4
 		rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
7e24c160
 		return false
 	}
 	daemon.netController.WalkNetworks(l)
06c4c5f4
 	return rep
7e24c160
 }
 
 // clusterNetworksPrune removes unused cluster networks
0dee6979
 func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
7e24c160
 	rep := &types.NetworksPruneReport{}
58738cde
 
06c4c5f4
 	until, _ := getUntilFromPruneFilters(pruneFilters)
58738cde
 
7e24c160
 	cluster := daemon.GetCluster()
4f2ed030
 
 	if !cluster.IsManager() {
 		return rep, nil
 	}
 
c0bc14e8
 	networks, err := cluster.GetNetworks(pruneFilters)
7e24c160
 	if err != nil {
 		return rep, err
 	}
 	networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
 	for _, nw := range networks {
0dee6979
 		select {
 		case <-ctx.Done():
87b4dc20
 			return rep, nil
0dee6979
 		default:
 			if nw.Ingress {
 				// Routing-mesh network removal has to be explicitly invoked by user
 				continue
7e24c160
 			}
0dee6979
 			if !until.IsZero() && nw.Created.After(until) {
 				continue
 			}
 			if !matchLabels(pruneFilters, nw.Labels) {
 				continue
 			}
 			// https://github.com/docker/docker/issues/24186
 			// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
 			// So we try to remove it anyway and check the error
 			err = cluster.RemoveNetwork(nw.ID)
 			if err != nil {
 				// we can safely ignore the "network .. is in use" error
 				match := networkIsInUse.FindStringSubmatch(err.Error())
 				if len(match) != 2 || match[1] != nw.ID {
 					logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
 				}
 				continue
 			}
 			rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
7e24c160
 		}
 	}
 	return rep, nil
 }
 
 // NetworksPrune removes unused networks
0dee6979
 func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
3279ca3c
 	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
2a8f46ab
 		return nil, errPruneRunning
3279ca3c
 	}
 	defer atomic.StoreInt32(&daemon.pruneRunning, 0)
 
71760ae6
 	// make sure that only accepted filters have been received
 	err := pruneFilters.Validate(networksAcceptedFilters)
 	if err != nil {
 		return nil, err
 	}
 
06c4c5f4
 	if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
 		return nil, err
7e24c160
 	}
06c4c5f4
 
 	rep := &types.NetworksPruneReport{}
0dee6979
 	if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil {
70252473
 		rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
 	}
06c4c5f4
 
0dee6979
 	localRep := daemon.localNetworksPrune(ctx, pruneFilters)
06c4c5f4
 	rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
0dee6979
 
 	select {
 	case <-ctx.Done():
87b4dc20
 		logrus.Debugf("NetworksPrune operation cancelled: %#v", *rep)
 		return rep, nil
0dee6979
 	default:
 	}
 
06c4c5f4
 	return rep, nil
7e24c160
 }
58738cde
 
 func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
 	until := time.Time{}
97c5ae25
 	if !pruneFilters.Contains("until") {
58738cde
 		return until, nil
 	}
 	untilFilters := pruneFilters.Get("until")
 	if len(untilFilters) > 1 {
 		return until, fmt.Errorf("more than one until filter specified")
 	}
 	ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
 	if err != nil {
 		return until, err
 	}
 	seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
 	if err != nil {
 		return until, err
 	}
 	until = time.Unix(seconds, nanoseconds)
 	return until, nil
 }
70252473
 
 func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {
 	if !pruneFilters.MatchKVList("label", labels) {
 		return false
 	}
 	// By default MatchKVList will return true if field (like 'label!') does not exist
97c5ae25
 	// So we have to add additional Contains("label!") check
 	if pruneFilters.Contains("label!") {
70252473
 		if pruneFilters.MatchKVList("label!", labels) {
 			return false
 		}
 	}
 	return true
 }