Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
| 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 |
+} |
| 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 |
+} |
| 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 |
+} |
| 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 |
+} |