prune.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. package daemon
  2. import (
  3. "fmt"
  4. "regexp"
  5. "runtime"
  6. "sync/atomic"
  7. "time"
  8. "github.com/docker/distribution/reference"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/filters"
  11. timetypes "github.com/docker/docker/api/types/time"
  12. "github.com/docker/docker/image"
  13. "github.com/docker/docker/layer"
  14. "github.com/docker/docker/pkg/directory"
  15. "github.com/docker/docker/pkg/system"
  16. "github.com/docker/docker/runconfig"
  17. "github.com/docker/docker/volume"
  18. "github.com/docker/libnetwork"
  19. digest "github.com/opencontainers/go-digest"
  20. "github.com/sirupsen/logrus"
  21. "golang.org/x/net/context"
  22. )
  23. var (
  24. // errPruneRunning is returned when a prune request is received while
  25. // one is in progress
  26. errPruneRunning = fmt.Errorf("a prune operation is already running")
  27. containersAcceptedFilters = map[string]bool{
  28. "label": true,
  29. "label!": true,
  30. "until": true,
  31. }
  32. volumesAcceptedFilters = map[string]bool{
  33. "label": true,
  34. "label!": true,
  35. }
  36. imagesAcceptedFilters = map[string]bool{
  37. "dangling": true,
  38. "label": true,
  39. "label!": true,
  40. "until": true,
  41. }
  42. networksAcceptedFilters = map[string]bool{
  43. "label": true,
  44. "label!": true,
  45. "until": true,
  46. }
  47. )
  48. // ContainersPrune removes unused containers
  49. func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
  50. if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
  51. return nil, errPruneRunning
  52. }
  53. defer atomic.StoreInt32(&daemon.pruneRunning, 0)
  54. rep := &types.ContainersPruneReport{}
  55. // make sure that only accepted filters have been received
  56. err := pruneFilters.Validate(containersAcceptedFilters)
  57. if err != nil {
  58. return nil, err
  59. }
  60. until, err := getUntilFromPruneFilters(pruneFilters)
  61. if err != nil {
  62. return nil, err
  63. }
  64. allContainers := daemon.List()
  65. for _, c := range allContainers {
  66. select {
  67. case <-ctx.Done():
  68. logrus.Debugf("ContainersPrune operation cancelled: %#v", *rep)
  69. return rep, nil
  70. default:
  71. }
  72. if !c.IsRunning() {
  73. if !until.IsZero() && c.Created.After(until) {
  74. continue
  75. }
  76. if !matchLabels(pruneFilters, c.Config.Labels) {
  77. continue
  78. }
  79. cSize, _ := daemon.getSize(c.ID)
  80. // TODO: sets RmLink to true?
  81. err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
  82. if err != nil {
  83. logrus.Warnf("failed to prune container %s: %v", c.ID, err)
  84. continue
  85. }
  86. if cSize > 0 {
  87. rep.SpaceReclaimed += uint64(cSize)
  88. }
  89. rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
  90. }
  91. }
  92. return rep, nil
  93. }
  94. // VolumesPrune removes unused local volumes
  95. func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
  96. if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
  97. return nil, errPruneRunning
  98. }
  99. defer atomic.StoreInt32(&daemon.pruneRunning, 0)
  100. // make sure that only accepted filters have been received
  101. err := pruneFilters.Validate(volumesAcceptedFilters)
  102. if err != nil {
  103. return nil, err
  104. }
  105. rep := &types.VolumesPruneReport{}
  106. pruneVols := func(v volume.Volume) error {
  107. select {
  108. case <-ctx.Done():
  109. logrus.Debugf("VolumesPrune operation cancelled: %#v", *rep)
  110. return ctx.Err()
  111. default:
  112. }
  113. name := v.Name()
  114. refs := daemon.volumes.Refs(v)
  115. if len(refs) == 0 {
  116. detailedVolume, ok := v.(volume.DetailedVolume)
  117. if ok {
  118. if !matchLabels(pruneFilters, detailedVolume.Labels()) {
  119. return nil
  120. }
  121. }
  122. vSize, err := directory.Size(v.Path())
  123. if err != nil {
  124. logrus.Warnf("could not determine size of volume %s: %v", name, err)
  125. }
  126. err = daemon.volumeRm(v)
  127. if err != nil {
  128. logrus.Warnf("could not remove volume %s: %v", name, err)
  129. return nil
  130. }
  131. rep.SpaceReclaimed += uint64(vSize)
  132. rep.VolumesDeleted = append(rep.VolumesDeleted, name)
  133. }
  134. return nil
  135. }
  136. err = daemon.traverseLocalVolumes(pruneVols)
  137. if err == context.Canceled {
  138. return rep, nil
  139. }
  140. return rep, err
  141. }
  142. // ImagesPrune removes unused images
  143. func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
  144. // TODO @jhowardmsft LCOW Support: This will need revisiting later.
  145. platform := runtime.GOOS
  146. if system.LCOWSupported() {
  147. platform = "linux"
  148. }
  149. if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
  150. return nil, errPruneRunning
  151. }
  152. defer atomic.StoreInt32(&daemon.pruneRunning, 0)
  153. // make sure that only accepted filters have been received
  154. err := pruneFilters.Validate(imagesAcceptedFilters)
  155. if err != nil {
  156. return nil, err
  157. }
  158. rep := &types.ImagesPruneReport{}
  159. danglingOnly := true
  160. if pruneFilters.Contains("dangling") {
  161. if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
  162. danglingOnly = false
  163. } else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
  164. return nil, invalidFilter{"dangling", pruneFilters.Get("dangling")}
  165. }
  166. }
  167. until, err := getUntilFromPruneFilters(pruneFilters)
  168. if err != nil {
  169. return nil, err
  170. }
  171. var allImages map[image.ID]*image.Image
  172. if danglingOnly {
  173. allImages = daemon.stores[platform].imageStore.Heads()
  174. } else {
  175. allImages = daemon.stores[platform].imageStore.Map()
  176. }
  177. allContainers := daemon.List()
  178. imageRefs := map[string]bool{}
  179. for _, c := range allContainers {
  180. select {
  181. case <-ctx.Done():
  182. return nil, ctx.Err()
  183. default:
  184. imageRefs[c.ID] = true
  185. }
  186. }
  187. // Filter intermediary images and get their unique size
  188. allLayers := daemon.stores[platform].layerStore.Map()
  189. topImages := map[image.ID]*image.Image{}
  190. for id, img := range allImages {
  191. select {
  192. case <-ctx.Done():
  193. return nil, ctx.Err()
  194. default:
  195. dgst := digest.Digest(id)
  196. if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.stores[platform].imageStore.Children(id)) != 0 {
  197. continue
  198. }
  199. if !until.IsZero() && img.Created.After(until) {
  200. continue
  201. }
  202. if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) {
  203. continue
  204. }
  205. topImages[id] = img
  206. }
  207. }
  208. canceled := false
  209. deleteImagesLoop:
  210. for id := range topImages {
  211. select {
  212. case <-ctx.Done():
  213. // we still want to calculate freed size and return the data
  214. canceled = true
  215. break deleteImagesLoop
  216. default:
  217. }
  218. dgst := digest.Digest(id)
  219. hex := dgst.Hex()
  220. if _, ok := imageRefs[hex]; ok {
  221. continue
  222. }
  223. deletedImages := []types.ImageDeleteResponseItem{}
  224. refs := daemon.referenceStore.References(dgst)
  225. if len(refs) > 0 {
  226. shouldDelete := !danglingOnly
  227. if !shouldDelete {
  228. hasTag := false
  229. for _, ref := range refs {
  230. if _, ok := ref.(reference.NamedTagged); ok {
  231. hasTag = true
  232. break
  233. }
  234. }
  235. // Only delete if it's untagged (i.e. repo:<none>)
  236. shouldDelete = !hasTag
  237. }
  238. if shouldDelete {
  239. for _, ref := range refs {
  240. imgDel, err := daemon.ImageDelete(ref.String(), false, true)
  241. if err != nil {
  242. logrus.Warnf("could not delete reference %s: %v", ref.String(), err)
  243. continue
  244. }
  245. deletedImages = append(deletedImages, imgDel...)
  246. }
  247. }
  248. } else {
  249. imgDel, err := daemon.ImageDelete(hex, false, true)
  250. if err != nil {
  251. logrus.Warnf("could not delete image %s: %v", hex, err)
  252. continue
  253. }
  254. deletedImages = append(deletedImages, imgDel...)
  255. }
  256. rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...)
  257. }
  258. // Compute how much space was freed
  259. for _, d := range rep.ImagesDeleted {
  260. if d.Deleted != "" {
  261. chid := layer.ChainID(d.Deleted)
  262. if l, ok := allLayers[chid]; ok {
  263. diffSize, err := l.DiffSize()
  264. if err != nil {
  265. logrus.Warnf("failed to get layer %s size: %v", chid, err)
  266. continue
  267. }
  268. rep.SpaceReclaimed += uint64(diffSize)
  269. }
  270. }
  271. }
  272. if canceled {
  273. logrus.Debugf("ImagesPrune operation cancelled: %#v", *rep)
  274. }
  275. return rep, nil
  276. }
  277. // localNetworksPrune removes unused local networks
  278. func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
  279. rep := &types.NetworksPruneReport{}
  280. until, _ := getUntilFromPruneFilters(pruneFilters)
  281. // When the function returns true, the walk will stop.
  282. l := func(nw libnetwork.Network) bool {
  283. select {
  284. case <-ctx.Done():
  285. // context cancelled
  286. return true
  287. default:
  288. }
  289. if nw.Info().ConfigOnly() {
  290. return false
  291. }
  292. if !until.IsZero() && nw.Info().Created().After(until) {
  293. return false
  294. }
  295. if !matchLabels(pruneFilters, nw.Info().Labels()) {
  296. return false
  297. }
  298. nwName := nw.Name()
  299. if runconfig.IsPreDefinedNetwork(nwName) {
  300. return false
  301. }
  302. if len(nw.Endpoints()) > 0 {
  303. return false
  304. }
  305. if err := daemon.DeleteNetwork(nw.ID()); err != nil {
  306. logrus.Warnf("could not remove local network %s: %v", nwName, err)
  307. return false
  308. }
  309. rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
  310. return false
  311. }
  312. daemon.netController.WalkNetworks(l)
  313. return rep
  314. }
  315. // clusterNetworksPrune removes unused cluster networks
  316. func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
  317. rep := &types.NetworksPruneReport{}
  318. until, _ := getUntilFromPruneFilters(pruneFilters)
  319. cluster := daemon.GetCluster()
  320. if !cluster.IsManager() {
  321. return rep, nil
  322. }
  323. networks, err := cluster.GetNetworks()
  324. if err != nil {
  325. return rep, err
  326. }
  327. networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
  328. for _, nw := range networks {
  329. select {
  330. case <-ctx.Done():
  331. return rep, nil
  332. default:
  333. if nw.Ingress {
  334. // Routing-mesh network removal has to be explicitly invoked by user
  335. continue
  336. }
  337. if !until.IsZero() && nw.Created.After(until) {
  338. continue
  339. }
  340. if !matchLabels(pruneFilters, nw.Labels) {
  341. continue
  342. }
  343. // https://github.com/docker/docker/issues/24186
  344. // `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
  345. // So we try to remove it anyway and check the error
  346. err = cluster.RemoveNetwork(nw.ID)
  347. if err != nil {
  348. // we can safely ignore the "network .. is in use" error
  349. match := networkIsInUse.FindStringSubmatch(err.Error())
  350. if len(match) != 2 || match[1] != nw.ID {
  351. logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
  352. }
  353. continue
  354. }
  355. rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
  356. }
  357. }
  358. return rep, nil
  359. }
  360. // NetworksPrune removes unused networks
  361. func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
  362. if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
  363. return nil, errPruneRunning
  364. }
  365. defer atomic.StoreInt32(&daemon.pruneRunning, 0)
  366. // make sure that only accepted filters have been received
  367. err := pruneFilters.Validate(networksAcceptedFilters)
  368. if err != nil {
  369. return nil, err
  370. }
  371. if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
  372. return nil, err
  373. }
  374. rep := &types.NetworksPruneReport{}
  375. if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil {
  376. rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
  377. }
  378. localRep := daemon.localNetworksPrune(ctx, pruneFilters)
  379. rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
  380. select {
  381. case <-ctx.Done():
  382. logrus.Debugf("NetworksPrune operation cancelled: %#v", *rep)
  383. return rep, nil
  384. default:
  385. }
  386. return rep, nil
  387. }
  388. func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
  389. until := time.Time{}
  390. if !pruneFilters.Contains("until") {
  391. return until, nil
  392. }
  393. untilFilters := pruneFilters.Get("until")
  394. if len(untilFilters) > 1 {
  395. return until, fmt.Errorf("more than one until filter specified")
  396. }
  397. ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
  398. if err != nil {
  399. return until, err
  400. }
  401. seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
  402. if err != nil {
  403. return until, err
  404. }
  405. until = time.Unix(seconds, nanoseconds)
  406. return until, nil
  407. }
  408. func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {
  409. if !pruneFilters.MatchKVList("label", labels) {
  410. return false
  411. }
  412. // By default MatchKVList will return true if field (like 'label!') does not exist
  413. // So we have to add additional Contains("label!") check
  414. if pruneFilters.Contains("label!") {
  415. if pruneFilters.MatchKVList("label!", labels) {
  416. return false
  417. }
  418. }
  419. return true
  420. }