Skip to content

Commit

Permalink
Add subcommand prune to the container, volume, image and system commands
Browse files Browse the repository at this point in the history
Signed-off-by: Kenfe-Mickael Laventure <[email protected]>
  • Loading branch information
mlaventure committed Sep 29, 2016
1 parent 33f4d68 commit 280c872
Show file tree
Hide file tree
Showing 16 changed files with 478 additions and 3 deletions.
1 change: 1 addition & 0 deletions cli/command/container/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
NewWaitCommand(dockerCli),
newListCommand(dockerCli),
newInspectCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}
74 changes: 74 additions & 0 deletions cli/command/container/prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package container

import (
"fmt"

"golang.org/x/net/context"

"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)

type pruneOptions struct {
force bool
}

// NewPruneCommand returns a new cobra prune command for containers
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts pruneOptions

cmd := &cobra.Command{
Use: "prune",
Short: "Remove all stopped containers",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(dockerCli, opts)
if err != nil {
return err
}
if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
}
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")

return cmd
}

const warning = `WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] `

func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}

report, err := dockerCli.Client().ContainersPrune(context.Background(), types.ContainersPruneConfig{})
if err != nil {
return
}

if len(report.ContainersDeleted) > 0 {
output = "Deleted Containers:"
for _, id := range report.ContainersDeleted {
output += id + "\n"
}
spaceReclaimed = report.SpaceReclaimed
}

return
}

// RunPrune call the Container Prune API
// This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true})
}
3 changes: 1 addition & 2 deletions cli/command/container/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/formatter"
"github.com/docker/docker/cli/command/system"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -110,7 +109,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
// retrieving the list of running containers to avoid a race where we
// would "miss" a creation.
started := make(chan struct{})
eh := system.InitEventHandler()
eh := command.InitEventHandler()
eh.Handle("create", func(e events.Message) {
if opts.all {
s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package system
package command

import (
"sync"
Expand Down
2 changes: 2 additions & 0 deletions cli/command/image/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
newListCommand(dockerCli),
newRemoveCommand(dockerCli),
newInspectCommand(dockerCli),
NewPruneCommand(dockerCli),
)

return cmd
}
90 changes: 90 additions & 0 deletions cli/command/image/prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package image

import (
"fmt"

"golang.org/x/net/context"

"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)

type pruneOptions struct {
force bool
all bool
}

// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts pruneOptions

cmd := &cobra.Command{
Use: "prune",
Short: "Remove unused images",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(dockerCli, opts)
if err != nil {
return err
}
if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
}
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images, not just dangling ones")

return cmd
}

const (
allImageWarning = `WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue?`
danglingWarning = `WARNING! This will remove all dangling images.
Are you sure you want to continue?`
)

func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
warning := danglingWarning
if opts.all {
warning = allImageWarning
}
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}

report, err := dockerCli.Client().ImagesPrune(context.Background(), types.ImagesPruneConfig{
DanglingOnly: !opts.all,
})
if err != nil {
return
}

if len(report.ImagesDeleted) > 0 {
output = "Deleted Images:\n"
for _, st := range report.ImagesDeleted {
if st.Untagged != "" {
output += fmt.Sprintln("untagged:", st.Untagged)
} else {
output += fmt.Sprintln("deleted:", st.Deleted)
}
}
spaceReclaimed = report.SpaceReclaimed
}

return
}

// RunPrune call the Image Prune API
// This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, all: all})
}
39 changes: 39 additions & 0 deletions cli/command/prune/prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package prune

import (
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/container"
"github.com/docker/docker/cli/command/image"
"github.com/docker/docker/cli/command/volume"
"github.com/spf13/cobra"
)

// NewContainerPruneCommand return a cobra prune command for containers
func NewContainerPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
return container.NewPruneCommand(dockerCli)
}

// NewVolumePruneCommand return a cobra prune command for volumes
func NewVolumePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
return volume.NewPruneCommand(dockerCli)
}

// NewImagePruneCommand return a cobra prune command for images
func NewImagePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
return image.NewPruneCommand(dockerCli)
}

// RunContainerPrune execute a prune command for containers
func RunContainerPrune(dockerCli *command.DockerCli) (uint64, string, error) {
return container.RunPrune(dockerCli)
}

// RunVolumePrune execute a prune command for volumes
func RunVolumePrune(dockerCli *command.DockerCli) (uint64, string, error) {
return volume.RunPrune(dockerCli)
}

// RunImagePrune execute a prune command for images
func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
return image.RunPrune(dockerCli, all)
}
1 change: 1 addition & 0 deletions cli/command/system/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd.AddCommand(
NewEventsCommand(dockerCli),
NewInfoCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}
90 changes: 90 additions & 0 deletions cli/command/system/prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package system

import (
"fmt"

"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/prune"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)

type pruneOptions struct {
force bool
all bool
}

// NewPruneCommand creates a new cobra.Command for `docker du`
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts pruneOptions

cmd := &cobra.Command{
Use: "prune [COMMAND]",
Short: "Remove unused data.",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runPrune(dockerCli, opts)
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images not just dangling ones")

return cmd
}

const (
warning = `WARNING! This will remove:
- all stopped containers
- all volumes not used by at least one container
%s
Are you sure you want to continue?`

danglingImageDesc = "- all dangling images"
allImageDesc = `- all images without at least one container associated to them`
)

func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
var message string

if opts.all {
message = fmt.Sprintf(warning, allImageDesc)
} else {
message = fmt.Sprintf(warning, danglingImageDesc)
}

if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), message) {
return nil
}

var spaceReclaimed uint64

for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
prune.RunContainerPrune,
prune.RunVolumePrune,
} {
spc, output, err := pruneFn(dockerCli)
if err != nil {
return err
}
if spc > 0 {
spaceReclaimed += spc
fmt.Fprintln(dockerCli.Out(), output)
}
}

spc, output, err := prune.RunImagePrune(dockerCli, opts.all)
if err != nil {
return err
}
if spc > 0 {
spaceReclaimed += spc
fmt.Fprintln(dockerCli.Out(), output)
}

fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))

return nil
}
22 changes: 22 additions & 0 deletions cli/command/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,25 @@ func PrettyPrint(i interface{}) string {
return capitalizeFirst(fmt.Sprintf("%s", t))
}
}

// PromptForConfirmation request and check confirmation from user.
// This will display the provided message followed by ' [y/N] '. If
// the user input 'y' or 'Y' it returns true other false. If no
// message is provided "Are you sure you want to proceeed? [y/N] "
// will be used instead.
func PromptForConfirmation(ins *InStream, outs *OutStream, message string) bool {
if message == "" {
message = "Are you sure you want to proceeed?"
}
message += " [y/N] "

fmt.Fprintf(outs, message)

answer := ""
n, _ := fmt.Fscan(ins, &answer)
if n != 1 || (answer != "y" && answer != "Y") {
return false
}

return true
}
1 change: 1 addition & 0 deletions cli/command/volume/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func NewVolumeCommand(dockerCli *command.DockerCli) *cobra.Command {
newInspectCommand(dockerCli),
newListCommand(dockerCli),
newRemoveCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}
Expand Down
Loading

0 comments on commit 280c872

Please sign in to comment.