github.com/moby/docker@v26.1.3+incompatible/daemon/prune.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "regexp" 6 "strconv" 7 "sync/atomic" 8 "time" 9 10 "github.com/containerd/log" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/backend" 13 "github.com/docker/docker/api/types/events" 14 "github.com/docker/docker/api/types/filters" 15 timetypes "github.com/docker/docker/api/types/time" 16 "github.com/docker/docker/errdefs" 17 "github.com/docker/docker/libnetwork" 18 "github.com/docker/docker/runconfig" 19 "github.com/pkg/errors" 20 ) 21 22 var ( 23 // errPruneRunning is returned when a prune request is received while 24 // one is in progress 25 errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running")) 26 27 containersAcceptedFilters = map[string]bool{ 28 "label": true, 29 "label!": true, 30 "until": true, 31 } 32 33 networksAcceptedFilters = map[string]bool{ 34 "label": true, 35 "label!": true, 36 "until": true, 37 } 38 ) 39 40 // ContainersPrune removes unused containers 41 func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) { 42 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 43 return nil, errPruneRunning 44 } 45 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 46 47 rep := &types.ContainersPruneReport{} 48 49 // make sure that only accepted filters have been received 50 err := pruneFilters.Validate(containersAcceptedFilters) 51 if err != nil { 52 return nil, err 53 } 54 55 until, err := getUntilFromPruneFilters(pruneFilters) 56 if err != nil { 57 return nil, err 58 } 59 60 cfg := &daemon.config().Config 61 allContainers := daemon.List() 62 for _, c := range allContainers { 63 select { 64 case <-ctx.Done(): 65 log.G(ctx).Debugf("ContainersPrune operation cancelled: %#v", *rep) 66 return rep, nil 67 default: 68 } 69 70 if !c.IsRunning() { 71 if !until.IsZero() && c.Created.After(until) { 72 continue 73 } 74 if !matchLabels(pruneFilters, c.Config.Labels) { 75 continue 76 } 77 cSize, _, err := daemon.imageService.GetContainerLayerSize(ctx, c.ID) 78 if err != nil { 79 return nil, err 80 } 81 // TODO: sets RmLink to true? 82 err = daemon.containerRm(cfg, c.ID, &backend.ContainerRmConfig{}) 83 if err != nil { 84 log.G(ctx).Warnf("failed to prune container %s: %v", c.ID, err) 85 continue 86 } 87 if cSize > 0 { 88 rep.SpaceReclaimed += uint64(cSize) 89 } 90 rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID) 91 } 92 } 93 daemon.EventsService.Log(events.ActionPrune, events.ContainerEventType, events.Actor{ 94 Attributes: map[string]string{"reclaimed": strconv.FormatUint(rep.SpaceReclaimed, 10)}, 95 }) 96 return rep, nil 97 } 98 99 // localNetworksPrune removes unused local networks 100 func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport { 101 rep := &types.NetworksPruneReport{} 102 103 until, _ := getUntilFromPruneFilters(pruneFilters) 104 105 // When the function returns true, the walk will stop. 106 daemon.netController.WalkNetworks(func(nw *libnetwork.Network) bool { 107 select { 108 case <-ctx.Done(): 109 // context cancelled 110 return true 111 default: 112 } 113 if nw.ConfigOnly() { 114 return false 115 } 116 if !until.IsZero() && nw.Created().After(until) { 117 return false 118 } 119 if !matchLabels(pruneFilters, nw.Labels()) { 120 return false 121 } 122 nwName := nw.Name() 123 if runconfig.IsPreDefinedNetwork(nwName) { 124 return false 125 } 126 if len(nw.Endpoints()) > 0 { 127 return false 128 } 129 if err := daemon.DeleteNetwork(nw.ID()); err != nil { 130 log.G(ctx).Warnf("could not remove local network %s: %v", nwName, err) 131 return false 132 } 133 rep.NetworksDeleted = append(rep.NetworksDeleted, nwName) 134 return false 135 }) 136 return rep 137 } 138 139 // clusterNetworksPrune removes unused cluster networks 140 func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { 141 rep := &types.NetworksPruneReport{} 142 143 until, _ := getUntilFromPruneFilters(pruneFilters) 144 145 cluster := daemon.GetCluster() 146 147 if !cluster.IsManager() { 148 return rep, nil 149 } 150 151 networks, err := cluster.GetNetworks(pruneFilters) 152 if err != nil { 153 return rep, err 154 } 155 networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`) 156 for _, nw := range networks { 157 select { 158 case <-ctx.Done(): 159 return rep, nil 160 default: 161 if nw.Ingress { 162 // Routing-mesh network removal has to be explicitly invoked by user 163 continue 164 } 165 if !until.IsZero() && nw.Created.After(until) { 166 continue 167 } 168 if !matchLabels(pruneFilters, nw.Labels) { 169 continue 170 } 171 // https://github.com/docker/docker/issues/24186 172 // `docker network inspect` unfortunately displays ONLY those containers that are local to that node. 173 // So we try to remove it anyway and check the error 174 err = cluster.RemoveNetwork(nw.ID) 175 if err != nil { 176 // we can safely ignore the "network .. is in use" error 177 match := networkIsInUse.FindStringSubmatch(err.Error()) 178 if len(match) != 2 || match[1] != nw.ID { 179 log.G(ctx).Warnf("could not remove cluster network %s: %v", nw.Name, err) 180 } 181 continue 182 } 183 rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name) 184 } 185 } 186 return rep, nil 187 } 188 189 // NetworksPrune removes unused networks 190 func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) { 191 if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) { 192 return nil, errPruneRunning 193 } 194 defer atomic.StoreInt32(&daemon.pruneRunning, 0) 195 196 // make sure that only accepted filters have been received 197 err := pruneFilters.Validate(networksAcceptedFilters) 198 if err != nil { 199 return nil, err 200 } 201 202 if _, err := getUntilFromPruneFilters(pruneFilters); err != nil { 203 return nil, err 204 } 205 206 rep := &types.NetworksPruneReport{} 207 if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil { 208 rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...) 209 } 210 211 localRep := daemon.localNetworksPrune(ctx, pruneFilters) 212 rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...) 213 214 select { 215 case <-ctx.Done(): 216 log.G(ctx).Debugf("NetworksPrune operation cancelled: %#v", *rep) 217 return rep, nil 218 default: 219 } 220 daemon.EventsService.Log(events.ActionPrune, events.NetworkEventType, events.Actor{ 221 Attributes: map[string]string{"reclaimed": "0"}, 222 }) 223 return rep, nil 224 } 225 226 func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) { 227 until := time.Time{} 228 if !pruneFilters.Contains("until") { 229 return until, nil 230 } 231 untilFilters := pruneFilters.Get("until") 232 if len(untilFilters) > 1 { 233 return until, errdefs.InvalidParameter(errors.New("more than one until filter specified")) 234 } 235 ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now()) 236 if err != nil { 237 return until, errdefs.InvalidParameter(err) 238 } 239 seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0) 240 if err != nil { 241 return until, errdefs.InvalidParameter(err) 242 } 243 until = time.Unix(seconds, nanoseconds) 244 return until, nil 245 } 246 247 func matchLabels(pruneFilters filters.Args, labels map[string]string) bool { 248 if !pruneFilters.MatchKVList("label", labels) { 249 return false 250 } 251 // By default MatchKVList will return true if field (like 'label!') does not exist 252 // So we have to add additional Contains("label!") check 253 if pruneFilters.Contains("label!") { 254 if pruneFilters.MatchKVList("label!", labels) { 255 return false 256 } 257 } 258 return true 259 }