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
} |