| package daemon // import "github.com/docker/docker/daemon" | |
| import ( | |
| "context" | |
| "regexp" | |
| "strconv" | |
| "sync/atomic" | |
| "time" | |
| "github.com/docker/docker/api/types" | |
| "github.com/docker/docker/api/types/events" | |
| "github.com/docker/docker/api/types/filters" | |
| timetypes "github.com/docker/docker/api/types/time" | |
| "github.com/docker/docker/errdefs" | |
| "github.com/docker/docker/libnetwork" | |
| "github.com/docker/docker/runconfig" | |
| "github.com/pkg/errors" | |
| "github.com/sirupsen/logrus" | |
| ) | |
| var ( | |
| // errPruneRunning is returned when a prune request is received while | |
| // one is in progress | |
| errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running")) | |
| containersAcceptedFilters = map[string]bool{ | |
| "label": true, | |
| "label!": true, | |
| "until": true, | |
| } | |
| networksAcceptedFilters = map[string]bool{ | |
| "label": true, | |
| "label!": true, | |
| "until": true, | |
| } | |
| ) | |
| // ContainersPrune removes unused containers | |
| func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) { | |
| if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { | |
| return nil, errPruneRunning | |
| } | |
| defer atomic.StoreInt32(&daemon.pruneRunning, 0) | |
| rep := &types.ContainersPruneReport{} | |
| // make sure that only accepted filters have been received | |
| err := pruneFilters.Validate(containersAcceptedFilters) | |
| if err != nil { | |
| return nil, err | |
| } | |
| until, err := getUntilFromPruneFilters(pruneFilters) | |
| if err != nil { | |
| return nil, err | |
| } | |
| allContainers := daemon.List() | |
| for _, c := range allContainers { | |
| select { | |
| case <-ctx.Done(): | |
| logrus.Debugf("ContainersPrune operation cancelled: %#v", *rep) | |
| return rep, nil | |
| default: | |
| } | |
| if !c.IsRunning() { | |
| if !until.IsZero() && c.Created.After(until) { | |
| continue | |
| } | |
| if !matchLabels(pruneFilters, c.Config.Labels) { | |
| continue | |
| } | |
| cSize, _, err := daemon.imageService.GetContainerLayerSize(ctx, c.ID) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // TODO: sets RmLink to true? | |
| err = daemon.ContainerRm(c.ID, &types.ContainerRmConfig{}) | |
| if err != nil { | |
| logrus.Warnf("failed to prune container %s: %v", c.ID, err) | |
| continue | |
| } | |
| if cSize > 0 { | |
| rep.SpaceReclaimed += uint64(cSize) | |
| } | |
| rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID) | |
| } | |
| } | |
| daemon.EventsService.Log("prune", events.ContainerEventType, events.Actor{ | |
| Attributes: map[string]string{"reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10)}, | |
| }) | |
| return rep, nil | |
| } | |
| // localNetworksPrune removes unused local networks | |
| func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport { | |
| rep := &types.NetworksPruneReport{} | |
| until, _ := getUntilFromPruneFilters(pruneFilters) | |
| // When the function returns true, the walk will stop. | |
| l := func(nw libnetwork.Network) bool { | |
| select { | |
| case <-ctx.Done(): | |
| // context cancelled | |
| return true | |
| default: | |
| } | |
| if nw.Info().ConfigOnly() { | |
| return false | |
| } | |
| if !until.IsZero() && nw.Info().Created().After(until) { | |
| return false | |
| } | |
| if !matchLabels(pruneFilters, nw.Info().Labels()) { | |
| return false | |
| } | |
| nwName := nw.Name() | |
| 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 | |
| } | |
| rep.NetworksDeleted = append(rep.NetworksDeleted, nwName) | |
| return false | |
| } | |
| daemon.netController.WalkNetworks(l) | |
| return rep | |
| } | |
| // clusterNetworksPrune removes unused cluster networks | |
| func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { | |
| rep := &types.NetworksPruneReport{} | |
| until, _ := getUntilFromPruneFilters(pruneFilters) | |
| cluster := daemon.GetCluster() | |
| if !cluster.IsManager() { | |
| return rep, nil | |
| } | |
| networks, err := cluster.GetNetworks(pruneFilters) | |
| if err != nil { | |
| return rep, err | |
| } | |
| networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`) | |
| for _, nw := range networks { | |
| select { | |
| case <-ctx.Done(): | |
| return rep, nil | |
| default: | |
| if nw.Ingress { | |
| // Routing-mesh network removal has to be explicitly invoked by user | |
| continue | |
| } | |
| 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) | |
| } | |
| } | |
| return rep, nil | |
| } | |
| // NetworksPrune removes unused networks | |
| func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { | |
| if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { | |
| return nil, errPruneRunning | |
| } | |
| defer atomic.StoreInt32(&daemon.pruneRunning, 0) | |
| // make sure that only accepted filters have been received | |
| err := pruneFilters.Validate(networksAcceptedFilters) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if _, err := getUntilFromPruneFilters(pruneFilters); err != nil { | |
| return nil, err | |
| } | |
| rep := &types.NetworksPruneReport{} | |
| if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil { | |
| rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...) | |
| } | |
| localRep := daemon.localNetworksPrune(ctx, pruneFilters) | |
| rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...) | |
| select { | |
| case <-ctx.Done(): | |
| logrus.Debugf("NetworksPrune operation cancelled: %#v", *rep) | |
| return rep, nil | |
| default: | |
| } | |
| daemon.EventsService.Log("prune", events.NetworkEventType, events.Actor{ | |
| Attributes: map[string]string{"reclaimed": "0"}, | |
| }) | |
| return rep, nil | |
| } | |
| func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { | |
| until := time.Time{} | |
| if !pruneFilters.Contains("until") { | |
| return until, nil | |
| } | |
| untilFilters := pruneFilters.Get("until") | |
| if len(untilFilters) > 1 { | |
| return until, errdefs.InvalidParameter(errors.New("more than one until filter specified")) | |
| } | |
| ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now()) | |
| if err != nil { | |
| return until, errdefs.InvalidParameter(err) | |
| } | |
| seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) | |
| if err != nil { | |
| return until, errdefs.InvalidParameter(err) | |
| } | |
| until = time.Unix(seconds, nanoseconds) | |
| return until, nil | |
| } | |
| 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 | |
| // So we have to add additional Contains("label!") check | |
| if pruneFilters.Contains("label!") { | |
| if pruneFilters.MatchKVList("label!", labels) { | |
| return false | |
| } | |
| } | |
| return true | |
| } |