d1d6357beb
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>
236 lines
6.8 KiB
Go
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
|
|
}
|