Skip to content

Commit 280c872

Browse files
committed
Add subcommand prune to the container, volume, image and system commands
Signed-off-by: Kenfe-Mickael Laventure <[email protected]>
1 parent 33f4d68 commit 280c872

16 files changed

Lines changed: 478 additions & 3 deletions

File tree

cli/command/container/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
4444
NewWaitCommand(dockerCli),
4545
newListCommand(dockerCli),
4646
newInspectCommand(dockerCli),
47+
NewPruneCommand(dockerCli),
4748
)
4849
return cmd
4950
}

cli/command/container/prune.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package container
2+
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/net/context"
7+
8+
"github.com/docker/docker/api/types"
9+
"github.com/docker/docker/cli"
10+
"github.com/docker/docker/cli/command"
11+
units "github.com/docker/go-units"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
type pruneOptions struct {
16+
force bool
17+
}
18+
19+
// NewPruneCommand returns a new cobra prune command for containers
20+
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
21+
var opts pruneOptions
22+
23+
cmd := &cobra.Command{
24+
Use: "prune",
25+
Short: "Remove all stopped containers",
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+
43+
return cmd
44+
}
45+
46+
const warning = `WARNING! This will remove all stopped containers.
47+
Are you sure you want to continue? [y/N] `
48+
49+
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
50+
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
51+
return
52+
}
53+
54+
report, err := dockerCli.Client().ContainersPrune(context.Background(), types.ContainersPruneConfig{})
55+
if err != nil {
56+
return
57+
}
58+
59+
if len(report.ContainersDeleted) > 0 {
60+
output = "Deleted Containers:"
61+
for _, id := range report.ContainersDeleted {
62+
output += id + "\n"
63+
}
64+
spaceReclaimed = report.SpaceReclaimed
65+
}
66+
67+
return
68+
}
69+
70+
// RunPrune call the Container Prune API
71+
// This returns the amount of space reclaimed and a detailed output string
72+
func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
73+
return runPrune(dockerCli, pruneOptions{force: true})
74+
}

cli/command/container/stats.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"github.com/docker/docker/cli"
1616
"github.com/docker/docker/cli/command"
1717
"github.com/docker/docker/cli/command/formatter"
18-
"github.com/docker/docker/cli/command/system"
1918
"github.com/spf13/cobra"
2019
)
2120

@@ -110,7 +109,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
110109
// retrieving the list of running containers to avoid a race where we
111110
// would "miss" a creation.
112111
started := make(chan struct{})
113-
eh := system.InitEventHandler()
112+
eh := command.InitEventHandler()
114113
eh.Handle("create", func(e events.Message) {
115114
if opts.all {
116115
s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package system
1+
package command
22

33
import (
44
"sync"

cli/command/image/cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
3131
newListCommand(dockerCli),
3232
newRemoveCommand(dockerCli),
3333
newInspectCommand(dockerCli),
34+
NewPruneCommand(dockerCli),
3435
)
36+
3537
return cmd
3638
}

cli/command/image/prune.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package image
2+
3+
import (
4+
"fmt"
5+
6+
"golang.org/x/net/context"
7+
8+
"github.com/docker/docker/api/types"
9+
"github.com/docker/docker/cli"
10+
"github.com/docker/docker/cli/command"
11+
units "github.com/docker/go-units"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
type pruneOptions struct {
16+
force bool
17+
all bool
18+
}
19+
20+
// NewPruneCommand returns a new cobra prune command for images
21+
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
22+
var opts pruneOptions
23+
24+
cmd := &cobra.Command{
25+
Use: "prune",
26+
Short: "Remove unused images",
27+
Args: cli.NoArgs,
28+
RunE: func(cmd *cobra.Command, args []string) error {
29+
spaceReclaimed, output, err := runPrune(dockerCli, opts)
30+
if err != nil {
31+
return err
32+
}
33+
if output != "" {
34+
fmt.Fprintln(dockerCli.Out(), output)
35+
}
36+
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
37+
return nil
38+
},
39+
}
40+
41+
flags := cmd.Flags()
42+
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
43+
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images, not just dangling ones")
44+
45+
return cmd
46+
}
47+
48+
const (
49+
allImageWarning = `WARNING! This will remove all images without at least one container associated to them.
50+
Are you sure you want to continue?`
51+
danglingWarning = `WARNING! This will remove all dangling images.
52+
Are you sure you want to continue?`
53+
)
54+
55+
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
56+
warning := danglingWarning
57+
if opts.all {
58+
warning = allImageWarning
59+
}
60+
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
61+
return
62+
}
63+
64+
report, err := dockerCli.Client().ImagesPrune(context.Background(), types.ImagesPruneConfig{
65+
DanglingOnly: !opts.all,
66+
})
67+
if err != nil {
68+
return
69+
}
70+
71+
if len(report.ImagesDeleted) > 0 {
72+
output = "Deleted Images:\n"
73+
for _, st := range report.ImagesDeleted {
74+
if st.Untagged != "" {
75+
output += fmt.Sprintln("untagged:", st.Untagged)
76+
} else {
77+
output += fmt.Sprintln("deleted:", st.Deleted)
78+
}
79+
}
80+
spaceReclaimed = report.SpaceReclaimed
81+
}
82+
83+
return
84+
}
85+
86+
// RunPrune call the Image Prune API
87+
// This returns the amount of space reclaimed and a detailed output string
88+
func RunPrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
89+
return runPrune(dockerCli, pruneOptions{force: true, all: all})
90+
}

cli/command/prune/prune.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package prune
2+
3+
import (
4+
"github.com/docker/docker/cli/command"
5+
"github.com/docker/docker/cli/command/container"
6+
"github.com/docker/docker/cli/command/image"
7+
"github.com/docker/docker/cli/command/volume"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
// NewContainerPruneCommand return a cobra prune command for containers
12+
func NewContainerPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
13+
return container.NewPruneCommand(dockerCli)
14+
}
15+
16+
// NewVolumePruneCommand return a cobra prune command for volumes
17+
func NewVolumePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
18+
return volume.NewPruneCommand(dockerCli)
19+
}
20+
21+
// NewImagePruneCommand return a cobra prune command for images
22+
func NewImagePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
23+
return image.NewPruneCommand(dockerCli)
24+
}
25+
26+
// RunContainerPrune execute a prune command for containers
27+
func RunContainerPrune(dockerCli *command.DockerCli) (uint64, string, error) {
28+
return container.RunPrune(dockerCli)
29+
}
30+
31+
// RunVolumePrune execute a prune command for volumes
32+
func RunVolumePrune(dockerCli *command.DockerCli) (uint64, string, error) {
33+
return volume.RunPrune(dockerCli)
34+
}
35+
36+
// RunImagePrune execute a prune command for images
37+
func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
38+
return image.RunPrune(dockerCli, all)
39+
}

cli/command/system/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
2222
cmd.AddCommand(
2323
NewEventsCommand(dockerCli),
2424
NewInfoCommand(dockerCli),
25+
NewPruneCommand(dockerCli),
2526
)
2627
return cmd
2728
}

cli/command/system/prune.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package system
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/docker/docker/cli"
7+
"github.com/docker/docker/cli/command"
8+
"github.com/docker/docker/cli/command/prune"
9+
units "github.com/docker/go-units"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
type pruneOptions struct {
14+
force bool
15+
all bool
16+
}
17+
18+
// NewPruneCommand creates a new cobra.Command for `docker du`
19+
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
20+
var opts pruneOptions
21+
22+
cmd := &cobra.Command{
23+
Use: "prune [COMMAND]",
24+
Short: "Remove unused data.",
25+
Args: cli.NoArgs,
26+
RunE: func(cmd *cobra.Command, args []string) error {
27+
return runPrune(dockerCli, opts)
28+
},
29+
}
30+
31+
flags := cmd.Flags()
32+
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
33+
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images not just dangling ones")
34+
35+
return cmd
36+
}
37+
38+
const (
39+
warning = `WARNING! This will remove:
40+
- all stopped containers
41+
- all volumes not used by at least one container
42+
%s
43+
Are you sure you want to continue?`
44+
45+
danglingImageDesc = "- all dangling images"
46+
allImageDesc = `- all images without at least one container associated to them`
47+
)
48+
49+
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
50+
var message string
51+
52+
if opts.all {
53+
message = fmt.Sprintf(warning, allImageDesc)
54+
} else {
55+
message = fmt.Sprintf(warning, danglingImageDesc)
56+
}
57+
58+
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), message) {
59+
return nil
60+
}
61+
62+
var spaceReclaimed uint64
63+
64+
for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
65+
prune.RunContainerPrune,
66+
prune.RunVolumePrune,
67+
} {
68+
spc, output, err := pruneFn(dockerCli)
69+
if err != nil {
70+
return err
71+
}
72+
if spc > 0 {
73+
spaceReclaimed += spc
74+
fmt.Fprintln(dockerCli.Out(), output)
75+
}
76+
}
77+
78+
spc, output, err := prune.RunImagePrune(dockerCli, opts.all)
79+
if err != nil {
80+
return err
81+
}
82+
if spc > 0 {
83+
spaceReclaimed += spc
84+
fmt.Fprintln(dockerCli.Out(), output)
85+
}
86+
87+
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
88+
89+
return nil
90+
}

cli/command/utils.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,25 @@ func PrettyPrint(i interface{}) string {
5757
return capitalizeFirst(fmt.Sprintf("%s", t))
5858
}
5959
}
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+
}

0 commit comments

Comments
 (0)