moby/daemon/prune.go
Yong Tang d1d6357beb Convert DanglingOnly to Filters for docker image prune
This fix convert DanglingOnly in ImagesPruneConfig to Filters,
so that it is possible to maintain API compatibility in the future.

Several integration tests have been added to cover changes.

This fix is related to 28497.

A follow up to this PR will be done once this PR is merged.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
(cherry picked from commit a6be56b54e)
Signed-off-by: Victor Vieux <vieux@docker.com>
2016-12-07 16:08:18 -08:00

236 lines
6.8 KiB
Go

package daemon
import (
"fmt"
"regexp"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/reference"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
"github.com/docker/libnetwork"
)
// ContainersPrune removes unused containers
func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
rep := &types.ContainersPruneReport{}
allContainers := daemon.List()
for _, c := range allContainers {
if !c.IsRunning() {
cSize, _ := daemon.getSize(c)
// 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)
}
}
return rep, nil
}
// VolumesPrune removes unused local volumes
func (daemon *Daemon) VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
rep := &types.VolumesPruneReport{}
pruneVols := func(v volume.Volume) error {
name := v.Name()
refs := daemon.volumes.Refs(v)
if len(refs) == 0 {
vSize, err := directory.Size(v.Path())
if err != nil {
logrus.Warnf("could not determine size of volume %s: %v", name, err)
}
err = daemon.volumes.Remove(v)
if err != nil {
logrus.Warnf("could not remove volume %s: %v", name, err)
return nil
}
rep.SpaceReclaimed += uint64(vSize)
rep.VolumesDeleted = append(rep.VolumesDeleted, name)
}
return nil
}
err := daemon.traverseLocalVolumes(pruneVols)
return rep, err
}
// ImagesPrune removes unused images
func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
rep := &types.ImagesPruneReport{}
danglingOnly := true
if pruneFilters.Include("dangling") {
if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
danglingOnly = false
} else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling"))
}
}
var allImages map[image.ID]*image.Image
if danglingOnly {
allImages = daemon.imageStore.Heads()
} else {
allImages = daemon.imageStore.Map()
}
allContainers := daemon.List()
imageRefs := map[string]bool{}
for _, c := range allContainers {
imageRefs[c.ID] = true
}
// Filter intermediary images and get their unique size
allLayers := daemon.layerStore.Map()
topImages := map[image.ID]*image.Image{}
for id, img := range allImages {
dgst := digest.Digest(id)
if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
continue
}
topImages[id] = img
}
for id := range topImages {
dgst := digest.Digest(id)
hex := dgst.Hex()
if _, ok := imageRefs[hex]; ok {
continue
}
deletedImages := []types.ImageDelete{}
refs := daemon.referenceStore.References(dgst)
if len(refs) > 0 {
if danglingOnly {
// Not a dangling image
continue
}
nrRefs := len(refs)
for _, ref := range refs {
// If nrRefs == 1, we have an image marked as myreponame:<none>
// i.e. the tag content was changed
if _, ok := ref.(reference.Canonical); ok && nrRefs > 1 {
continue
}
imgDel, err := daemon.ImageDelete(ref.String(), false, true)
if err != nil {
logrus.Warnf("could not delete reference %s: %v", ref.String(), err)
continue
}
deletedImages = append(deletedImages, imgDel...)
}
} else {
imgDel, err := daemon.ImageDelete(hex, false, true)
if err != nil {
logrus.Warnf("could not delete image %s: %v", hex, err)
continue
}
deletedImages = append(deletedImages, imgDel...)
}
rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...)
}
// Compute how much space was freed
for _, d := range rep.ImagesDeleted {
if d.Deleted != "" {
chid := layer.ChainID(d.Deleted)
if l, ok := allLayers[chid]; ok {
diffSize, err := l.DiffSize()
if err != nil {
logrus.Warnf("failed to get layer %s size: %v", chid, err)
continue
}
rep.SpaceReclaimed += uint64(diffSize)
}
}
}
return rep, nil
}
// localNetworksPrune removes unused local networks
func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
rep := &types.NetworksPruneReport{}
var err error
// When the function returns true, the walk will stop.
l := func(nw libnetwork.Network) bool {
nwName := nw.Name()
predefined := runconfig.IsPreDefinedNetwork(nwName)
if !predefined && len(nw.Endpoints()) == 0 {
if err = daemon.DeleteNetwork(nw.ID()); err != nil {
logrus.Warnf("could not remove network %s: %v", nwName, err)
return false
}
rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
}
return false
}
daemon.netController.WalkNetworks(l)
return rep, err
}
// clusterNetworksPrune removes unused cluster networks
func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
rep := &types.NetworksPruneReport{}
cluster := daemon.GetCluster()
networks, err := cluster.GetNetworks()
if err != nil {
return rep, err
}
networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
for _, nw := range networks {
if nw.Name == "ingress" {
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 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(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
rep := &types.NetworksPruneReport{}
clusterRep, err := daemon.clusterNetworksPrune(pruneFilters)
if err != nil {
logrus.Warnf("could not remove cluster networks: %v", err)
} else {
rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
}
localRep, err := daemon.localNetworksPrune(pruneFilters)
if err != nil {
logrus.Warnf("could not remove local networks: %v", err)
} else {
rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
}
return rep, err
}