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