Browse code

Add subcommand prune to the container, volume, image and system commands

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

Kenfe-Mickael Laventure authored on 2016/09/23 06:04:34
Showing 17 changed files
... ...
@@ -44,6 +44,7 @@ func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
44 44
 		NewWaitCommand(dockerCli),
45 45
 		newListCommand(dockerCli),
46 46
 		newInspectCommand(dockerCli),
47
+		NewPruneCommand(dockerCli),
47 48
 	)
48 49
 	return cmd
49 50
 }
50 51
new file mode 100644
... ...
@@ -0,0 +1,74 @@
0
+package container
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"golang.org/x/net/context"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/docker/docker/cli/command"
10
+	units "github.com/docker/go-units"
11
+	"github.com/spf13/cobra"
12
+)
13
+
14
+type pruneOptions struct {
15
+	force bool
16
+}
17
+
18
+// NewPruneCommand returns a new cobra prune command for containers
19
+func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
20
+	var opts pruneOptions
21
+
22
+	cmd := &cobra.Command{
23
+		Use:   "prune",
24
+		Short: "Remove all stopped containers",
25
+		Args:  cli.NoArgs,
26
+		RunE: func(cmd *cobra.Command, args []string) error {
27
+			spaceReclaimed, output, err := runPrune(dockerCli, opts)
28
+			if err != nil {
29
+				return err
30
+			}
31
+			if output != "" {
32
+				fmt.Fprintln(dockerCli.Out(), output)
33
+			}
34
+			fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
35
+			return nil
36
+		},
37
+	}
38
+
39
+	flags := cmd.Flags()
40
+	flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
41
+
42
+	return cmd
43
+}
44
+
45
+const warning = `WARNING! This will remove all stopped containers.
46
+Are you sure you want to continue? [y/N] `
47
+
48
+func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
49
+	if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
50
+		return
51
+	}
52
+
53
+	report, err := dockerCli.Client().ContainersPrune(context.Background(), types.ContainersPruneConfig{})
54
+	if err != nil {
55
+		return
56
+	}
57
+
58
+	if len(report.ContainersDeleted) > 0 {
59
+		output = "Deleted Containers:"
60
+		for _, id := range report.ContainersDeleted {
61
+			output += id + "\n"
62
+		}
63
+		spaceReclaimed = report.SpaceReclaimed
64
+	}
65
+
66
+	return
67
+}
68
+
69
+// RunPrune call the Container Prune API
70
+// This returns the amount of space reclaimed and a detailed output string
71
+func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
72
+	return runPrune(dockerCli, pruneOptions{force: true})
73
+}
... ...
@@ -15,7 +15,6 @@ import (
15 15
 	"github.com/docker/docker/cli"
16 16
 	"github.com/docker/docker/cli/command"
17 17
 	"github.com/docker/docker/cli/command/formatter"
18
-	"github.com/docker/docker/cli/command/system"
19 18
 	"github.com/spf13/cobra"
20 19
 )
21 20
 
... ...
@@ -110,7 +109,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
110 110
 		// retrieving the list of running containers to avoid a race where we
111 111
 		// would "miss" a creation.
112 112
 		started := make(chan struct{})
113
-		eh := system.InitEventHandler()
113
+		eh := command.InitEventHandler()
114 114
 		eh.Handle("create", func(e events.Message) {
115 115
 			if opts.all {
116 116
 				s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
117 117
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package command
1
+
2
+import (
3
+	"sync"
4
+
5
+	"github.com/Sirupsen/logrus"
6
+	eventtypes "github.com/docker/docker/api/types/events"
7
+)
8
+
9
+type eventProcessor func(eventtypes.Message, error) error
10
+
11
+// EventHandler is abstract interface for user to customize
12
+// own handle functions of each type of events
13
+type EventHandler interface {
14
+	Handle(action string, h func(eventtypes.Message))
15
+	Watch(c <-chan eventtypes.Message)
16
+}
17
+
18
+// InitEventHandler initializes and returns an EventHandler
19
+func InitEventHandler() EventHandler {
20
+	return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
21
+}
22
+
23
+type eventHandler struct {
24
+	handlers map[string]func(eventtypes.Message)
25
+	mu       sync.Mutex
26
+}
27
+
28
+func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
29
+	w.mu.Lock()
30
+	w.handlers[action] = h
31
+	w.mu.Unlock()
32
+}
33
+
34
+// Watch ranges over the passed in event chan and processes the events based on the
35
+// handlers created for a given action.
36
+// To stop watching, close the event chan.
37
+func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
38
+	for e := range c {
39
+		w.mu.Lock()
40
+		h, exists := w.handlers[e.Action]
41
+		w.mu.Unlock()
42
+		if !exists {
43
+			continue
44
+		}
45
+		logrus.Debugf("event handler: received event: %v", e)
46
+		go h(e)
47
+	}
48
+}
... ...
@@ -31,6 +31,8 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
31 31
 		newListCommand(dockerCli),
32 32
 		newRemoveCommand(dockerCli),
33 33
 		newInspectCommand(dockerCli),
34
+		NewPruneCommand(dockerCli),
34 35
 	)
36
+
35 37
 	return cmd
36 38
 }
37 39
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package image
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"golang.org/x/net/context"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/docker/docker/cli/command"
10
+	units "github.com/docker/go-units"
11
+	"github.com/spf13/cobra"
12
+)
13
+
14
+type pruneOptions struct {
15
+	force bool
16
+	all   bool
17
+}
18
+
19
+// NewPruneCommand returns a new cobra prune command for images
20
+func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
21
+	var opts pruneOptions
22
+
23
+	cmd := &cobra.Command{
24
+		Use:   "prune",
25
+		Short: "Remove unused images",
26
+		Args:  cli.NoArgs,
27
+		RunE: func(cmd *cobra.Command, args []string) error {
28
+			spaceReclaimed, output, err := runPrune(dockerCli, opts)
29
+			if err != nil {
30
+				return err
31
+			}
32
+			if output != "" {
33
+				fmt.Fprintln(dockerCli.Out(), output)
34
+			}
35
+			fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
36
+			return nil
37
+		},
38
+	}
39
+
40
+	flags := cmd.Flags()
41
+	flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
42
+	flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images, not just dangling ones")
43
+
44
+	return cmd
45
+}
46
+
47
+const (
48
+	allImageWarning = `WARNING! This will remove all images without at least one container associated to them.
49
+Are you sure you want to continue?`
50
+	danglingWarning = `WARNING! This will remove all dangling images.
51
+Are you sure you want to continue?`
52
+)
53
+
54
+func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
55
+	warning := danglingWarning
56
+	if opts.all {
57
+		warning = allImageWarning
58
+	}
59
+	if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
60
+		return
61
+	}
62
+
63
+	report, err := dockerCli.Client().ImagesPrune(context.Background(), types.ImagesPruneConfig{
64
+		DanglingOnly: !opts.all,
65
+	})
66
+	if err != nil {
67
+		return
68
+	}
69
+
70
+	if len(report.ImagesDeleted) > 0 {
71
+		output = "Deleted Images:\n"
72
+		for _, st := range report.ImagesDeleted {
73
+			if st.Untagged != "" {
74
+				output += fmt.Sprintln("untagged:", st.Untagged)
75
+			} else {
76
+				output += fmt.Sprintln("deleted:", st.Deleted)
77
+			}
78
+		}
79
+		spaceReclaimed = report.SpaceReclaimed
80
+	}
81
+
82
+	return
83
+}
84
+
85
+// RunPrune call the Image Prune API
86
+// This returns the amount of space reclaimed and a detailed output string
87
+func RunPrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
88
+	return runPrune(dockerCli, pruneOptions{force: true, all: all})
89
+}
0 90
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+package prune
1
+
2
+import (
3
+	"github.com/docker/docker/cli/command"
4
+	"github.com/docker/docker/cli/command/container"
5
+	"github.com/docker/docker/cli/command/image"
6
+	"github.com/docker/docker/cli/command/volume"
7
+	"github.com/spf13/cobra"
8
+)
9
+
10
+// NewContainerPruneCommand return a cobra prune command for containers
11
+func NewContainerPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
12
+	return container.NewPruneCommand(dockerCli)
13
+}
14
+
15
+// NewVolumePruneCommand return a cobra prune command for volumes
16
+func NewVolumePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
17
+	return volume.NewPruneCommand(dockerCli)
18
+}
19
+
20
+// NewImagePruneCommand return a cobra prune command for images
21
+func NewImagePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
22
+	return image.NewPruneCommand(dockerCli)
23
+}
24
+
25
+// RunContainerPrune execute a prune command for containers
26
+func RunContainerPrune(dockerCli *command.DockerCli) (uint64, string, error) {
27
+	return container.RunPrune(dockerCli)
28
+}
29
+
30
+// RunVolumePrune execute a prune command for volumes
31
+func RunVolumePrune(dockerCli *command.DockerCli) (uint64, string, error) {
32
+	return volume.RunPrune(dockerCli)
33
+}
34
+
35
+// RunImagePrune execute a prune command for images
36
+func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
37
+	return image.RunPrune(dockerCli, all)
38
+}
... ...
@@ -22,6 +22,7 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
22 22
 	cmd.AddCommand(
23 23
 		NewEventsCommand(dockerCli),
24 24
 		NewInfoCommand(dockerCli),
25
+		NewPruneCommand(dockerCli),
25 26
 	)
26 27
 	return cmd
27 28
 }
28 29
deleted file mode 100644
... ...
@@ -1,49 +0,0 @@
1
-package system
2
-
3
-import (
4
-	"sync"
5
-
6
-	"github.com/Sirupsen/logrus"
7
-	eventtypes "github.com/docker/docker/api/types/events"
8
-)
9
-
10
-type eventProcessor func(eventtypes.Message, error) error
11
-
12
-// EventHandler is abstract interface for user to customize
13
-// own handle functions of each type of events
14
-type EventHandler interface {
15
-	Handle(action string, h func(eventtypes.Message))
16
-	Watch(c <-chan eventtypes.Message)
17
-}
18
-
19
-// InitEventHandler initializes and returns an EventHandler
20
-func InitEventHandler() EventHandler {
21
-	return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
22
-}
23
-
24
-type eventHandler struct {
25
-	handlers map[string]func(eventtypes.Message)
26
-	mu       sync.Mutex
27
-}
28
-
29
-func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
30
-	w.mu.Lock()
31
-	w.handlers[action] = h
32
-	w.mu.Unlock()
33
-}
34
-
35
-// Watch ranges over the passed in event chan and processes the events based on the
36
-// handlers created for a given action.
37
-// To stop watching, close the event chan.
38
-func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
39
-	for e := range c {
40
-		w.mu.Lock()
41
-		h, exists := w.handlers[e.Action]
42
-		w.mu.Unlock()
43
-		if !exists {
44
-			continue
45
-		}
46
-		logrus.Debugf("event handler: received event: %v", e)
47
-		go h(e)
48
-	}
49
-}
50 1
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package system
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/docker/cli"
6
+	"github.com/docker/docker/cli/command"
7
+	"github.com/docker/docker/cli/command/prune"
8
+	units "github.com/docker/go-units"
9
+	"github.com/spf13/cobra"
10
+)
11
+
12
+type pruneOptions struct {
13
+	force bool
14
+	all   bool
15
+}
16
+
17
+// NewPruneCommand creates a new cobra.Command for `docker du`
18
+func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
19
+	var opts pruneOptions
20
+
21
+	cmd := &cobra.Command{
22
+		Use:   "prune [COMMAND]",
23
+		Short: "Remove unused data.",
24
+		Args:  cli.NoArgs,
25
+		RunE: func(cmd *cobra.Command, args []string) error {
26
+			return runPrune(dockerCli, opts)
27
+		},
28
+	}
29
+
30
+	flags := cmd.Flags()
31
+	flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
32
+	flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images not just dangling ones")
33
+
34
+	return cmd
35
+}
36
+
37
+const (
38
+	warning = `WARNING! This will remove:
39
+	- all stopped containers
40
+	- all volumes not used by at least one container
41
+	%s
42
+Are you sure you want to continue?`
43
+
44
+	danglingImageDesc = "- all dangling images"
45
+	allImageDesc      = `- all images without at least one container associated to them`
46
+)
47
+
48
+func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
49
+	var message string
50
+
51
+	if opts.all {
52
+		message = fmt.Sprintf(warning, allImageDesc)
53
+	} else {
54
+		message = fmt.Sprintf(warning, danglingImageDesc)
55
+	}
56
+
57
+	if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), message) {
58
+		return nil
59
+	}
60
+
61
+	var spaceReclaimed uint64
62
+
63
+	for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
64
+		prune.RunContainerPrune,
65
+		prune.RunVolumePrune,
66
+	} {
67
+		spc, output, err := pruneFn(dockerCli)
68
+		if err != nil {
69
+			return err
70
+		}
71
+		if spc > 0 {
72
+			spaceReclaimed += spc
73
+			fmt.Fprintln(dockerCli.Out(), output)
74
+		}
75
+	}
76
+
77
+	spc, output, err := prune.RunImagePrune(dockerCli, opts.all)
78
+	if err != nil {
79
+		return err
80
+	}
81
+	if spc > 0 {
82
+		spaceReclaimed += spc
83
+		fmt.Fprintln(dockerCli.Out(), output)
84
+	}
85
+
86
+	fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
87
+
88
+	return nil
89
+}
... ...
@@ -57,3 +57,25 @@ func PrettyPrint(i interface{}) string {
57 57
 		return capitalizeFirst(fmt.Sprintf("%s", t))
58 58
 	}
59 59
 }
60
+
61
+// PromptForConfirmation request and check confirmation from user.
62
+// This will display the provided message followed by ' [y/N] '. If
63
+// the user input 'y' or 'Y' it returns true other false.  If no
64
+// message is provided "Are you sure you want to proceeed? [y/N] "
65
+// will be used instead.
66
+func PromptForConfirmation(ins *InStream, outs *OutStream, message string) bool {
67
+	if message == "" {
68
+		message = "Are you sure you want to proceeed?"
69
+	}
70
+	message += " [y/N] "
71
+
72
+	fmt.Fprintf(outs, message)
73
+
74
+	answer := ""
75
+	n, _ := fmt.Fscan(ins, &answer)
76
+	if n != 1 || (answer != "y" && answer != "Y") {
77
+		return false
78
+	}
79
+
80
+	return true
81
+}
... ...
@@ -25,6 +25,7 @@ func NewVolumeCommand(dockerCli *command.DockerCli) *cobra.Command {
25 25
 		newInspectCommand(dockerCli),
26 26
 		newListCommand(dockerCli),
27 27
 		newRemoveCommand(dockerCli),
28
+		NewPruneCommand(dockerCli),
28 29
 	)
29 30
 	return cmd
30 31
 }
31 32
new file mode 100644
... ...
@@ -0,0 +1,74 @@
0
+package volume
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"golang.org/x/net/context"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/docker/docker/cli/command"
10
+	units "github.com/docker/go-units"
11
+	"github.com/spf13/cobra"
12
+)
13
+
14
+type pruneOptions struct {
15
+	force bool
16
+}
17
+
18
+// NewPruneCommand returns a new cobra prune command for volumes
19
+func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
20
+	var opts pruneOptions
21
+
22
+	cmd := &cobra.Command{
23
+		Use:   "prune",
24
+		Short: "Remove all unused volumes",
25
+		Args:  cli.NoArgs,
26
+		RunE: func(cmd *cobra.Command, args []string) error {
27
+			spaceReclaimed, output, err := runPrune(dockerCli, opts)
28
+			if err != nil {
29
+				return err
30
+			}
31
+			if output != "" {
32
+				fmt.Fprintln(dockerCli.Out(), output)
33
+			}
34
+			fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
35
+			return nil
36
+		},
37
+	}
38
+
39
+	flags := cmd.Flags()
40
+	flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
41
+
42
+	return cmd
43
+}
44
+
45
+const warning = `WARNING! This will remove all volumes not used by at least one container.
46
+Are you sure you want to continue?`
47
+
48
+func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
49
+	if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
50
+		return
51
+	}
52
+
53
+	report, err := dockerCli.Client().VolumesPrune(context.Background(), types.VolumesPruneConfig{})
54
+	if err != nil {
55
+		return
56
+	}
57
+
58
+	if len(report.VolumesDeleted) > 0 {
59
+		output = "Deleted Volumes:\n"
60
+		for _, id := range report.VolumesDeleted {
61
+			output += id + "\n"
62
+		}
63
+		spaceReclaimed = report.SpaceReclaimed
64
+	}
65
+
66
+	return
67
+}
68
+
69
+// RunPrune call the Volume Prune API
70
+// This returns the amount of space reclaimed and a detailed output string
71
+func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
72
+	return runPrune(dockerCli, pruneOptions{force: true})
73
+}
0 74
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"golang.org/x/net/context"
8
+)
9
+
10
+// ContainersPrune requests the daemon to delete unused data
11
+func (cli *Client) ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error) {
12
+	var report types.ContainersPruneReport
13
+
14
+	serverResp, err := cli.post(ctx, "/containers/prune", nil, cfg, nil)
15
+	if err != nil {
16
+		return report, err
17
+	}
18
+	defer ensureReaderClosed(serverResp)
19
+
20
+	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
21
+		return report, fmt.Errorf("Error retrieving disk usage: %v", err)
22
+	}
23
+
24
+	return report, nil
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"golang.org/x/net/context"
8
+)
9
+
10
+// ImagesPrune requests the daemon to delete unused data
11
+func (cli *Client) ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error) {
12
+	var report types.ImagesPruneReport
13
+
14
+	serverResp, err := cli.post(ctx, "/images/prune", nil, cfg, nil)
15
+	if err != nil {
16
+		return report, err
17
+	}
18
+	defer ensureReaderClosed(serverResp)
19
+
20
+	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
21
+		return report, fmt.Errorf("Error retrieving disk usage: %v", err)
22
+	}
23
+
24
+	return report, nil
25
+}
... ...
@@ -61,6 +61,7 @@ type ContainerAPIClient interface {
61 61
 	ContainerWait(ctx context.Context, container string) (int, error)
62 62
 	CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
63 63
 	CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
64
+	ContainersPrune(ctx context.Context, cfg types.ContainersPruneConfig) (types.ContainersPruneReport, error)
64 65
 }
65 66
 
66 67
 // ImageAPIClient defines API client methods for the images
... ...
@@ -78,6 +79,7 @@ type ImageAPIClient interface {
78 78
 	ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error)
79 79
 	ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
80 80
 	ImageTag(ctx context.Context, image, ref string) error
81
+	ImagesPrune(ctx context.Context, cfg types.ImagesPruneConfig) (types.ImagesPruneReport, error)
81 82
 }
82 83
 
83 84
 // NetworkAPIClient defines API client methods for the networks
... ...
@@ -124,6 +126,7 @@ type SystemAPIClient interface {
124 124
 	Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error)
125 125
 	Info(ctx context.Context) (types.Info, error)
126 126
 	RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error)
127
+	DiskUsage(ctx context.Context) (types.DiskUsage, error)
127 128
 }
128 129
 
129 130
 // VolumeAPIClient defines API client methods for the volumes
... ...
@@ -133,4 +136,5 @@ type VolumeAPIClient interface {
133 133
 	VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error)
134 134
 	VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error)
135 135
 	VolumeRemove(ctx context.Context, volumeID string, force bool) error
136
+	VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error)
136 137
 }
137 138
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"golang.org/x/net/context"
8
+)
9
+
10
+// VolumesPrune requests the daemon to delete unused data
11
+func (cli *Client) VolumesPrune(ctx context.Context, cfg types.VolumesPruneConfig) (types.VolumesPruneReport, error) {
12
+	var report types.VolumesPruneReport
13
+
14
+	serverResp, err := cli.post(ctx, "/volumes/prune", nil, cfg, nil)
15
+	if err != nil {
16
+		return report, err
17
+	}
18
+	defer ensureReaderClosed(serverResp)
19
+
20
+	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
21
+		return report, fmt.Errorf("Error retrieving disk usage: %v", err)
22
+	}
23
+
24
+	return report, nil
25
+}