Merge pull request #29226 from yongtang/28535-prune-until-follow-up
Add `--filter until=<timestamp>` for `docker container/image prune`
This commit is contained in:
commit
f1fdbeca2a
16 changed files with 671 additions and 49 deletions
|
@ -312,7 +312,12 @@ func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWr
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneReport, err := n.backend.NetworksPrune(filters.Args{})
|
pruneFilters, err := filters.FromParam(r.Form.Get("filters"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pruneReport, err := n.backend.NetworksPrune(pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4191,6 +4191,7 @@ paths:
|
||||||
Filters to process on the prune list, encoded as JSON (a `map[string][]string`).
|
Filters to process on the prune list, encoded as JSON (a `map[string][]string`).
|
||||||
|
|
||||||
Available filters:
|
Available filters:
|
||||||
|
- `until=<timestamp>` Prune containers created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time.
|
||||||
type: "string"
|
type: "string"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
@ -4866,6 +4867,7 @@ paths:
|
||||||
- `dangling=<boolean>` When set to `true` (or `1`), prune only
|
- `dangling=<boolean>` When set to `true` (or `1`), prune only
|
||||||
unused *and* untagged images. When set to `false`
|
unused *and* untagged images. When set to `false`
|
||||||
(or `0`), all unused images are pruned.
|
(or `0`), all unused images are pruned.
|
||||||
|
- `until=<string>` Prune images created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time.
|
||||||
type: "string"
|
type: "string"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
@ -6295,8 +6297,6 @@ paths:
|
||||||
/networks/prune:
|
/networks/prune:
|
||||||
post:
|
post:
|
||||||
summary: "Delete unused networks"
|
summary: "Delete unused networks"
|
||||||
consumes:
|
|
||||||
- "application/json"
|
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
operationId: "NetworkPrune"
|
operationId: "NetworkPrune"
|
||||||
|
@ -6307,6 +6307,7 @@ paths:
|
||||||
Filters to process on the prune list, encoded as JSON (a `map[string][]string`).
|
Filters to process on the prune list, encoded as JSON (a `map[string][]string`).
|
||||||
|
|
||||||
Available filters:
|
Available filters:
|
||||||
|
- `until=<timestamp>` Prune networks created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time.
|
||||||
type: "string"
|
type: "string"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
|
|
@ -3,21 +3,22 @@ package container
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for containers
|
// NewPruneCommand returns a new cobra prune command for containers
|
||||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts pruneOptions
|
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
|
@ -39,6 +40,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
|
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -47,11 +49,13 @@ const warning = `WARNING! This will remove all stopped containers.
|
||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
|
|
||||||
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
|
pruneFilters := opts.filter.Value()
|
||||||
|
|
||||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
report, err := dockerCli.Client().ContainersPrune(context.Background(), filters.Args{})
|
report, err := dockerCli.Client().ContainersPrune(context.Background(), pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -69,6 +73,6 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u
|
||||||
|
|
||||||
// RunPrune calls the Container Prune API
|
// RunPrune calls the Container Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
|
func RunPrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return runPrune(dockerCli, pruneOptions{force: true})
|
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,21 +5,22 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
all bool
|
all bool
|
||||||
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for images
|
// NewPruneCommand returns a new cobra prune command for images
|
||||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts pruneOptions
|
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
|
@ -42,6 +43,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
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")
|
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
||||||
|
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -54,7 +56,7 @@ Are you sure you want to continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := filters.NewArgs()
|
pruneFilters := opts.filter.Value()
|
||||||
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
|
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
|
||||||
|
|
||||||
warning := danglingWarning
|
warning := danglingWarning
|
||||||
|
@ -87,6 +89,6 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u
|
||||||
|
|
||||||
// RunPrune calls the Image Prune API
|
// RunPrune calls the Image Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
|
func RunPrune(dockerCli *command.DockerCli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return runPrune(dockerCli, pruneOptions{force: true, all: all})
|
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,20 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for networks
|
// NewPruneCommand returns a new cobra prune command for networks
|
||||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts pruneOptions
|
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
|
@ -38,6 +39,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
|
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -46,11 +48,13 @@ const warning = `WARNING! This will remove all networks not used by at least one
|
||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
|
|
||||||
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, err error) {
|
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, err error) {
|
||||||
|
pruneFilters := opts.filter.Value()
|
||||||
|
|
||||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
report, err := dockerCli.Client().NetworksPrune(context.Background(), filters.Args{})
|
report, err := dockerCli.Client().NetworksPrune(context.Background(), pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -67,7 +71,7 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, e
|
||||||
|
|
||||||
// RunPrune calls the Network Prune API
|
// RunPrune calls the Network Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
|
func RunPrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
output, err := runPrune(dockerCli, pruneOptions{force: true})
|
output, err := runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
||||||
return 0, output, err
|
return 0, output, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/docker/docker/cli/command/image"
|
"github.com/docker/docker/cli/command/image"
|
||||||
"github.com/docker/docker/cli/command/network"
|
"github.com/docker/docker/cli/command/network"
|
||||||
"github.com/docker/docker/cli/command/volume"
|
"github.com/docker/docker/cli/command/volume"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,21 +31,21 @@ func NewNetworkPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunContainerPrune executes a prune command for containers
|
// RunContainerPrune executes a prune command for containers
|
||||||
func RunContainerPrune(dockerCli *command.DockerCli) (uint64, string, error) {
|
func RunContainerPrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return container.RunPrune(dockerCli)
|
return container.RunPrune(dockerCli, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunVolumePrune executes a prune command for volumes
|
// RunVolumePrune executes a prune command for volumes
|
||||||
func RunVolumePrune(dockerCli *command.DockerCli) (uint64, string, error) {
|
func RunVolumePrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return volume.RunPrune(dockerCli)
|
return volume.RunPrune(dockerCli)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunImagePrune executes a prune command for images
|
// RunImagePrune executes a prune command for images
|
||||||
func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
|
func RunImagePrune(dockerCli *command.DockerCli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return image.RunPrune(dockerCli, all)
|
return image.RunPrune(dockerCli, all, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunNetworkPrune executes a prune command for networks
|
// RunNetworkPrune executes a prune command for networks
|
||||||
func RunNetworkPrune(dockerCli *command.DockerCli) (uint64, string, error) {
|
func RunNetworkPrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return network.RunPrune(dockerCli)
|
return network.RunPrune(dockerCli, filter)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,20 @@ import (
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
"github.com/docker/docker/cli/command/prune"
|
"github.com/docker/docker/cli/command/prune"
|
||||||
|
"github.com/docker/docker/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
all bool
|
all bool
|
||||||
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand creates a new cobra.Command for `docker prune`
|
// NewPruneCommand creates a new cobra.Command for `docker prune`
|
||||||
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts pruneOptions
|
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
|
@ -32,6 +34,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
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")
|
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images not just dangling ones")
|
||||||
|
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -48,27 +51,27 @@ Are you sure you want to continue?`
|
||||||
allImageDesc = `- all images without at least one container associated to them`
|
allImageDesc = `- all images without at least one container associated to them`
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
|
func runPrune(dockerCli *command.DockerCli, options pruneOptions) error {
|
||||||
var message string
|
var message string
|
||||||
|
|
||||||
if opts.all {
|
if options.all {
|
||||||
message = fmt.Sprintf(warning, allImageDesc)
|
message = fmt.Sprintf(warning, allImageDesc)
|
||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf(warning, danglingImageDesc)
|
message = fmt.Sprintf(warning, danglingImageDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), message) {
|
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), message) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var spaceReclaimed uint64
|
var spaceReclaimed uint64
|
||||||
|
|
||||||
for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
|
for _, pruneFn := range []func(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error){
|
||||||
prune.RunContainerPrune,
|
prune.RunContainerPrune,
|
||||||
prune.RunVolumePrune,
|
prune.RunVolumePrune,
|
||||||
prune.RunNetworkPrune,
|
prune.RunNetworkPrune,
|
||||||
} {
|
} {
|
||||||
spc, output, err := pruneFn(dockerCli)
|
spc, output, err := pruneFn(dockerCli, options.filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -78,7 +81,7 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spc, output, err := prune.RunImagePrune(dockerCli, opts.all)
|
spc, output, err := prune.RunImagePrune(dockerCli, options.all, options.filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
111
client/container_prune_test.go
Normal file
111
client/container_prune_test.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/pkg/testutil/assert"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContainersPruneError(t *testing.T) {
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
||||||
|
version: "1.25",
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
|
||||||
|
_, err := client.ContainersPrune(context.Background(), filters)
|
||||||
|
assert.Error(t, err, "Error response from daemon: Server error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainersPrune(t *testing.T) {
|
||||||
|
expectedURL := "/v1.25/containers/prune"
|
||||||
|
|
||||||
|
danglingFilters := filters.NewArgs()
|
||||||
|
danglingFilters.Add("dangling", "true")
|
||||||
|
|
||||||
|
noDanglingFilters := filters.NewArgs()
|
||||||
|
noDanglingFilters.Add("dangling", "false")
|
||||||
|
|
||||||
|
danglingUntilFilters := filters.NewArgs()
|
||||||
|
danglingUntilFilters.Add("dangling", "true")
|
||||||
|
danglingUntilFilters.Add("until", "2016-12-15T14:00")
|
||||||
|
|
||||||
|
listCases := []struct {
|
||||||
|
filters filters.Args
|
||||||
|
expectedQueryParams map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
filters: filters.Args{},
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: danglingFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"true":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: danglingUntilFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"true":true},"until":{"2016-12-15T14:00":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: noDanglingFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"false":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, listCase := range listCases {
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if !strings.HasPrefix(req.URL.Path, expectedURL) {
|
||||||
|
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
|
||||||
|
}
|
||||||
|
query := req.URL.Query()
|
||||||
|
for key, expected := range listCase.expectedQueryParams {
|
||||||
|
actual := query.Get(key)
|
||||||
|
assert.Equal(t, actual, expected)
|
||||||
|
}
|
||||||
|
content, err := json.Marshal(types.ContainersPruneReport{
|
||||||
|
ContainersDeleted: []string{"container_id1", "container_id2"},
|
||||||
|
SpaceReclaimed: 9999,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader(content)),
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
version: "1.25",
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := client.ContainersPrune(context.Background(), listCase.filters)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, len(report.ContainersDeleted), 2)
|
||||||
|
assert.Equal(t, report.SpaceReclaimed, uint64(9999))
|
||||||
|
}
|
||||||
|
}
|
106
client/image_prune_test.go
Normal file
106
client/image_prune_test.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/pkg/testutil/assert"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImagesPruneError(t *testing.T) {
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
||||||
|
version: "1.25",
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
|
||||||
|
_, err := client.ImagesPrune(context.Background(), filters)
|
||||||
|
assert.Error(t, err, "Error response from daemon: Server error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImagesPrune(t *testing.T) {
|
||||||
|
expectedURL := "/v1.25/images/prune"
|
||||||
|
|
||||||
|
danglingFilters := filters.NewArgs()
|
||||||
|
danglingFilters.Add("dangling", "true")
|
||||||
|
|
||||||
|
noDanglingFilters := filters.NewArgs()
|
||||||
|
noDanglingFilters.Add("dangling", "false")
|
||||||
|
|
||||||
|
listCases := []struct {
|
||||||
|
filters filters.Args
|
||||||
|
expectedQueryParams map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
filters: filters.Args{},
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: danglingFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"true":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: noDanglingFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"false":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, listCase := range listCases {
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if !strings.HasPrefix(req.URL.Path, expectedURL) {
|
||||||
|
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
|
||||||
|
}
|
||||||
|
query := req.URL.Query()
|
||||||
|
for key, expected := range listCase.expectedQueryParams {
|
||||||
|
actual := query.Get(key)
|
||||||
|
assert.Equal(t, actual, expected)
|
||||||
|
}
|
||||||
|
content, err := json.Marshal(types.ImagesPruneReport{
|
||||||
|
ImagesDeleted: []types.ImageDelete{
|
||||||
|
{
|
||||||
|
Deleted: "image_id1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Deleted: "image_id2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SpaceReclaimed: 9999,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader(content)),
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
version: "1.25",
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := client.ImagesPrune(context.Background(), listCase.filters)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, len(report.ImagesDeleted), 2)
|
||||||
|
assert.Equal(t, report.SpaceReclaimed, uint64(9999))
|
||||||
|
}
|
||||||
|
}
|
99
client/network_prune_test.go
Normal file
99
client/network_prune_test.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/pkg/testutil/assert"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNetworksPruneError(t *testing.T) {
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
||||||
|
version: "1.25",
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
|
||||||
|
_, err := client.NetworksPrune(context.Background(), filters)
|
||||||
|
if err == nil || err.Error() != "Error response from daemon: Server error" {
|
||||||
|
t.Fatalf("expected a Server Error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworksPrune(t *testing.T) {
|
||||||
|
expectedURL := "/v1.25/networks/prune"
|
||||||
|
|
||||||
|
danglingFilters := filters.NewArgs()
|
||||||
|
danglingFilters.Add("dangling", "true")
|
||||||
|
|
||||||
|
noDanglingFilters := filters.NewArgs()
|
||||||
|
noDanglingFilters.Add("dangling", "false")
|
||||||
|
|
||||||
|
listCases := []struct {
|
||||||
|
filters filters.Args
|
||||||
|
expectedQueryParams map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
filters: filters.Args{},
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: danglingFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"true":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filters: noDanglingFilters,
|
||||||
|
expectedQueryParams: map[string]string{
|
||||||
|
"until": "",
|
||||||
|
"filter": "",
|
||||||
|
"filters": `{"dangling":{"false":true}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, listCase := range listCases {
|
||||||
|
client := &Client{
|
||||||
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if !strings.HasPrefix(req.URL.Path, expectedURL) {
|
||||||
|
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
|
||||||
|
}
|
||||||
|
query := req.URL.Query()
|
||||||
|
for key, expected := range listCase.expectedQueryParams {
|
||||||
|
actual := query.Get(key)
|
||||||
|
assert.Equal(t, actual, expected)
|
||||||
|
}
|
||||||
|
content, err := json.Marshal(types.NetworksPruneReport{
|
||||||
|
NetworksDeleted: []string{"network_id1", "network_id2"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: ioutil.NopCloser(bytes.NewReader(content)),
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
version: "1.25",
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := client.NetworksPrune(context.Background(), listCase.filters)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, len(report.NetworksDeleted), 2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,12 @@ package daemon
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
timetypes "github.com/docker/docker/api/types/time"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/layer"
|
"github.com/docker/docker/layer"
|
||||||
"github.com/docker/docker/pkg/directory"
|
"github.com/docker/docker/pkg/directory"
|
||||||
|
@ -21,9 +23,17 @@ import (
|
||||||
func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
|
func (daemon *Daemon) ContainersPrune(pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
|
||||||
rep := &types.ContainersPruneReport{}
|
rep := &types.ContainersPruneReport{}
|
||||||
|
|
||||||
|
until, err := getUntilFromPruneFilters(pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
allContainers := daemon.List()
|
allContainers := daemon.List()
|
||||||
for _, c := range allContainers {
|
for _, c := range allContainers {
|
||||||
if !c.IsRunning() {
|
if !c.IsRunning() {
|
||||||
|
if !until.IsZero() && c.Created.After(until) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
cSize, _ := daemon.getSize(c)
|
cSize, _ := daemon.getSize(c)
|
||||||
// TODO: sets RmLink to true?
|
// TODO: sets RmLink to true?
|
||||||
err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
|
err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
|
||||||
|
@ -84,6 +94,11 @@ func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPrune
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
until, err := getUntilFromPruneFilters(pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var allImages map[image.ID]*image.Image
|
var allImages map[image.ID]*image.Image
|
||||||
if danglingOnly {
|
if danglingOnly {
|
||||||
allImages = daemon.imageStore.Heads()
|
allImages = daemon.imageStore.Heads()
|
||||||
|
@ -104,6 +119,9 @@ func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPrune
|
||||||
if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
|
if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !until.IsZero() && img.Created.After(until) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
topImages[id] = img
|
topImages[id] = img
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,9 +187,17 @@ func (daemon *Daemon) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPrune
|
||||||
// localNetworksPrune removes unused local networks
|
// localNetworksPrune removes unused local networks
|
||||||
func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
|
func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
|
||||||
rep := &types.NetworksPruneReport{}
|
rep := &types.NetworksPruneReport{}
|
||||||
var err error
|
|
||||||
|
until, err := getUntilFromPruneFilters(pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return rep, err
|
||||||
|
}
|
||||||
|
|
||||||
// When the function returns true, the walk will stop.
|
// When the function returns true, the walk will stop.
|
||||||
l := func(nw libnetwork.Network) bool {
|
l := func(nw libnetwork.Network) bool {
|
||||||
|
if !until.IsZero() && nw.Info().Created().After(until) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
nwName := nw.Name()
|
nwName := nw.Name()
|
||||||
predefined := runconfig.IsPreDefinedNetwork(nwName)
|
predefined := runconfig.IsPreDefinedNetwork(nwName)
|
||||||
if !predefined && len(nw.Endpoints()) == 0 {
|
if !predefined && len(nw.Endpoints()) == 0 {
|
||||||
|
@ -190,6 +216,12 @@ func (daemon *Daemon) localNetworksPrune(pruneFilters filters.Args) (*types.Netw
|
||||||
// clusterNetworksPrune removes unused cluster networks
|
// clusterNetworksPrune removes unused cluster networks
|
||||||
func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
|
func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
|
||||||
rep := &types.NetworksPruneReport{}
|
rep := &types.NetworksPruneReport{}
|
||||||
|
|
||||||
|
until, err := getUntilFromPruneFilters(pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cluster := daemon.GetCluster()
|
cluster := daemon.GetCluster()
|
||||||
networks, err := cluster.GetNetworks()
|
networks, err := cluster.GetNetworks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -200,6 +232,9 @@ func (daemon *Daemon) clusterNetworksPrune(pruneFilters filters.Args) (*types.Ne
|
||||||
if nw.Name == "ingress" {
|
if nw.Name == "ingress" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !until.IsZero() && nw.Created.After(until) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// https://github.com/docker/docker/issues/24186
|
// https://github.com/docker/docker/issues/24186
|
||||||
// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
|
// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
|
||||||
// So we try to remove it anyway and check the error
|
// So we try to remove it anyway and check the error
|
||||||
|
@ -234,3 +269,24 @@ func (daemon *Daemon) NetworksPrune(pruneFilters filters.Args) (*types.NetworksP
|
||||||
}
|
}
|
||||||
return rep, err
|
return rep, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
|
||||||
|
until := time.Time{}
|
||||||
|
if !pruneFilters.Include("until") {
|
||||||
|
return until, nil
|
||||||
|
}
|
||||||
|
untilFilters := pruneFilters.Get("until")
|
||||||
|
if len(untilFilters) > 1 {
|
||||||
|
return until, fmt.Errorf("more than one until filter specified")
|
||||||
|
}
|
||||||
|
ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return until, err
|
||||||
|
}
|
||||||
|
seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
|
||||||
|
if err != nil {
|
||||||
|
return until, err
|
||||||
|
}
|
||||||
|
until = time.Unix(seconds, nanoseconds)
|
||||||
|
return until, nil
|
||||||
|
}
|
||||||
|
|
|
@ -21,8 +21,10 @@ Usage: docker container prune [OPTIONS]
|
||||||
Remove all stopped containers
|
Remove all stopped containers
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-f, --force Do not prompt for confirmation
|
Options:
|
||||||
--help Print usage
|
--filter filter Provide filter values (e.g. 'until=<timestamp>')
|
||||||
|
-f, --force Do not prompt for confirmation
|
||||||
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
@ -38,6 +40,63 @@ f98f9c2aa1eaf727e4ec9c0283bc7d4aa4762fbdba7f26191f26c97f64090360
|
||||||
Total reclaimed space: 212 B
|
Total reclaimed space: 212 B
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filtering
|
||||||
|
|
||||||
|
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||||
|
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||||
|
|
||||||
|
The currently supported filters are:
|
||||||
|
|
||||||
|
* until (`<timestamp>`) - only remove containers created before given timestamp
|
||||||
|
|
||||||
|
The `until` filter can be Unix timestamps, date formatted
|
||||||
|
timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
|
||||||
|
relative to the daemon machine’s time. Supported formats for date
|
||||||
|
formatted time stamps include RFC3339Nano, RFC3339, `2006-01-02T15:04:05`,
|
||||||
|
`2006-01-02T15:04:05.999999999`, `2006-01-02Z07:00`, and `2006-01-02`. The local
|
||||||
|
timezone on the daemon will be used if you do not provide either a `Z` or a
|
||||||
|
`+-00:00` timezone offset at the end of the timestamp. When providing Unix
|
||||||
|
timestamps enter seconds[.nanoseconds], where seconds is the number of seconds
|
||||||
|
that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap
|
||||||
|
seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||||
|
fraction of a second no more than nine digits long.
|
||||||
|
|
||||||
|
The following removes containers created more than 5 minutes ago:
|
||||||
|
```bash
|
||||||
|
$ docker ps -a --format 'table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}'
|
||||||
|
CONTAINER ID IMAGE COMMAND CREATED AT STATUS
|
||||||
|
61b9efa71024 busybox "sh" 2017-01-04 13:23:33 -0800 PST Exited (0) 41 seconds ago
|
||||||
|
53a9bc23a516 busybox "sh" 2017-01-04 13:11:59 -0800 PST Exited (0) 12 minutes ago
|
||||||
|
|
||||||
|
$ docker container prune --force --filter "until=5m"
|
||||||
|
Deleted Containers:
|
||||||
|
53a9bc23a5168b6caa2bfbefddf1b30f93c7ad57f3dec271fd32707497cb9369
|
||||||
|
|
||||||
|
Total reclaimed space: 25 B
|
||||||
|
|
||||||
|
$ docker ps -a --format 'table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}'
|
||||||
|
CONTAINER ID IMAGE COMMAND CREATED AT STATUS
|
||||||
|
61b9efa71024 busybox "sh" 2017-01-04 13:23:33 -0800 PST Exited (0) 44 seconds ago
|
||||||
|
```
|
||||||
|
|
||||||
|
The following removes containers created before `2017-01-04T13:10:00`:
|
||||||
|
```bash
|
||||||
|
$ docker ps -a --format 'table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}'
|
||||||
|
CONTAINER ID IMAGE COMMAND CREATED AT STATUS
|
||||||
|
53a9bc23a516 busybox "sh" 2017-01-04 13:11:59 -0800 PST Exited (0) 7 minutes ago
|
||||||
|
4a75091a6d61 busybox "sh" 2017-01-04 13:09:53 -0800 PST Exited (0) 9 minutes ago
|
||||||
|
|
||||||
|
$ docker container prune --force --filter "until=2017-01-04T13:10:00"
|
||||||
|
Deleted Containers:
|
||||||
|
4a75091a6d618526fcd8b33ccd6e5928ca2a64415466f768a6180004b0c72c6c
|
||||||
|
|
||||||
|
Total reclaimed space: 27 B
|
||||||
|
|
||||||
|
$ docker ps -a --format 'table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}'
|
||||||
|
CONTAINER ID IMAGE COMMAND CREATED AT STATUS
|
||||||
|
53a9bc23a516 busybox "sh" 2017-01-04 13:11:59 -0800 PST Exited (0) 9 minutes ago
|
||||||
|
```
|
||||||
|
|
||||||
## Related information
|
## Related information
|
||||||
|
|
||||||
* [system df](system_df.md)
|
* [system df](system_df.md)
|
||||||
|
|
|
@ -21,9 +21,10 @@ Usage: docker image prune [OPTIONS]
|
||||||
Remove unused images
|
Remove unused images
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-a, --all Remove all unused images, not just dangling ones
|
-a, --all Remove all unused images, not just dangling ones
|
||||||
-f, --force Do not prompt for confirmation
|
--filter filter Provide filter values (e.g. 'until=<timestamp>')
|
||||||
--help Print usage
|
-f, --force Do not prompt for confirmation
|
||||||
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
Remove all dangling images. If `-a` is specified, will also remove all images not referenced by any container.
|
Remove all dangling images. If `-a` is specified, will also remove all images not referenced by any container.
|
||||||
|
@ -62,6 +63,87 @@ deleted: sha256:2c675ee9ed53425e31a13e3390bf3f539bf8637000e4bcfbb85ee03ef4d910a1
|
||||||
Total reclaimed space: 16.43 MB
|
Total reclaimed space: 16.43 MB
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filtering
|
||||||
|
|
||||||
|
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||||
|
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||||
|
|
||||||
|
The currently supported filters are:
|
||||||
|
|
||||||
|
* until (`<timestamp>`) - only remove images created before given timestamp
|
||||||
|
|
||||||
|
The `until` filter can be Unix timestamps, date formatted
|
||||||
|
timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
|
||||||
|
relative to the daemon machine’s time. Supported formats for date
|
||||||
|
formatted time stamps include RFC3339Nano, RFC3339, `2006-01-02T15:04:05`,
|
||||||
|
`2006-01-02T15:04:05.999999999`, `2006-01-02Z07:00`, and `2006-01-02`. The local
|
||||||
|
timezone on the daemon will be used if you do not provide either a `Z` or a
|
||||||
|
`+-00:00` timezone offset at the end of the timestamp. When providing Unix
|
||||||
|
timestamps enter seconds[.nanoseconds], where seconds is the number of seconds
|
||||||
|
that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap
|
||||||
|
seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||||
|
fraction of a second no more than nine digits long.
|
||||||
|
|
||||||
|
The following removes images created before `2017-01-04T00:00:00`:
|
||||||
|
```bash
|
||||||
|
$ docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}'
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED AT SIZE
|
||||||
|
foo latest 2f287ac753da 2017-01-04 13:42:23 -0800 PST 3.98 MB
|
||||||
|
alpine latest 88e169ea8f46 2016-12-27 10:17:25 -0800 PST 3.98 MB
|
||||||
|
busybox latest e02e811dd08f 2016-10-07 14:03:58 -0700 PDT 1.09 MB
|
||||||
|
|
||||||
|
$ docker image prune -a --force --filter "until=2017-01-04T00:00:00"
|
||||||
|
Deleted Images:
|
||||||
|
untagged: alpine:latest
|
||||||
|
untagged: alpine@sha256:dfbd4a3a8ebca874ebd2474f044a0b33600d4523d03b0df76e5c5986cb02d7e8
|
||||||
|
untagged: busybox:latest
|
||||||
|
untagged: busybox@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
|
||||||
|
deleted: sha256:e02e811dd08fd49e7f6032625495118e63f597eb150403d02e3238af1df240ba
|
||||||
|
deleted: sha256:e88b3f82283bc59d5e0df427c824e9f95557e661fcb0ea15fb0fb6f97760f9d9
|
||||||
|
|
||||||
|
Total reclaimed space: 1.093 MB
|
||||||
|
|
||||||
|
$ docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}'
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED AT SIZE
|
||||||
|
foo latest 2f287ac753da 2017-01-04 13:42:23 -0800 PST 3.98 MB
|
||||||
|
```
|
||||||
|
|
||||||
|
The following removes images created more than 10 days (`240h`) ago:
|
||||||
|
```bash
|
||||||
|
$ docker images
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
|
foo latest 2f287ac753da 14 seconds ago 3.98 MB
|
||||||
|
alpine latest 88e169ea8f46 8 days ago 3.98 MB
|
||||||
|
debian jessie 7b0a06c805e8 2 months ago 123 MB
|
||||||
|
busybox latest e02e811dd08f 2 months ago 1.09 MB
|
||||||
|
golang 1.7.0 138c2e655421 4 months ago 670 MB
|
||||||
|
|
||||||
|
$ docker image prune -a --force --filter "until=240h"
|
||||||
|
Deleted Images:
|
||||||
|
untagged: golang:1.7.0
|
||||||
|
untagged: golang@sha256:6765038c2b8f407fd6e3ecea043b44580c229ccfa2a13f6d85866cf2b4a9628e
|
||||||
|
deleted: sha256:138c2e6554219de65614d88c15521bfb2da674cbb0bf840de161f89ff4264b96
|
||||||
|
deleted: sha256:ec353c2e1a673f456c4b78906d0d77f9d9456cfb5229b78c6a960bfb7496b76a
|
||||||
|
deleted: sha256:fe22765feaf3907526b4921c73ea6643ff9e334497c9b7e177972cf22f68ee93
|
||||||
|
deleted: sha256:ff845959c80148421a5c3ae11cc0e6c115f950c89bc949646be55ed18d6a2912
|
||||||
|
deleted: sha256:a4320831346648c03db64149eafc83092e2b34ab50ca6e8c13112388f25899a7
|
||||||
|
deleted: sha256:4c76020202ee1d9709e703b7c6de367b325139e74eebd6b55b30a63c196abaf3
|
||||||
|
deleted: sha256:d7afd92fb07236c8a2045715a86b7d5f0066cef025018cd3ca9a45498c51d1d6
|
||||||
|
deleted: sha256:9e63c5bce4585dd7038d830a1f1f4e44cb1a1515b00e620ac718e934b484c938
|
||||||
|
untagged: debian:jessie
|
||||||
|
untagged: debian@sha256:c1af755d300d0c65bb1194d24bce561d70c98a54fb5ce5b1693beb4f7988272f
|
||||||
|
deleted: sha256:7b0a06c805e8f23807fb8856621c60851727e85c7bcb751012c813f122734c8d
|
||||||
|
deleted: sha256:f96222d75c5563900bc4dd852179b720a0885de8f7a0619ba0ac76e92542bbc8
|
||||||
|
|
||||||
|
Total reclaimed space: 792.6 MB
|
||||||
|
|
||||||
|
$ docker images
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
|
foo latest 2f287ac753da About a minute ago 3.98 MB
|
||||||
|
alpine latest 88e169ea8f46 8 days ago 3.98 MB
|
||||||
|
busybox latest e02e811dd08f 2 months ago 1.09 MB
|
||||||
|
```
|
||||||
|
|
||||||
## Related information
|
## Related information
|
||||||
|
|
||||||
* [system df](system_df.md)
|
* [system df](system_df.md)
|
||||||
|
|
|
@ -12,8 +12,9 @@ Usage: docker network prune [OPTIONS]
|
||||||
Remove all unused networks
|
Remove all unused networks
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-f, --force Do not prompt for confirmation
|
--filter filter Provide filter values (e.g. 'until=<timestamp>')
|
||||||
--help Print usage
|
-f, --force Do not prompt for confirmation
|
||||||
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
Remove all unused networks. Unused networks are those which are not referenced by any containers.
|
Remove all unused networks. Unused networks are those which are not referenced by any containers.
|
||||||
|
@ -29,6 +30,51 @@ n1
|
||||||
n2
|
n2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filtering
|
||||||
|
|
||||||
|
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||||
|
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||||
|
|
||||||
|
The currently supported filters are:
|
||||||
|
|
||||||
|
* until (`<timestamp>`) - only remove networks created before given timestamp
|
||||||
|
|
||||||
|
The `until` filter can be Unix timestamps, date formatted
|
||||||
|
timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
|
||||||
|
relative to the daemon machine’s time. Supported formats for date
|
||||||
|
formatted time stamps include RFC3339Nano, RFC3339, `2006-01-02T15:04:05`,
|
||||||
|
`2006-01-02T15:04:05.999999999`, `2006-01-02Z07:00`, and `2006-01-02`. The local
|
||||||
|
timezone on the daemon will be used if you do not provide either a `Z` or a
|
||||||
|
`+-00:00` timezone offset at the end of the timestamp. When providing Unix
|
||||||
|
timestamps enter seconds[.nanoseconds], where seconds is the number of seconds
|
||||||
|
that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap
|
||||||
|
seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||||
|
fraction of a second no more than nine digits long.
|
||||||
|
|
||||||
|
The following removes networks created more than 5 minutes ago. Note that
|
||||||
|
system networks such as `bridge`, `host`, and `none` will never be pruned:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker network ls
|
||||||
|
NETWORK ID NAME DRIVER SCOPE
|
||||||
|
7430df902d7a bridge bridge local
|
||||||
|
ea92373fd499 foo-1-day-ago bridge local
|
||||||
|
ab53663ed3c7 foo-1-min-ago bridge local
|
||||||
|
97b91972bc3b host host local
|
||||||
|
f949d337b1f5 none null local
|
||||||
|
|
||||||
|
$ docker network prune --force --filter until=5m
|
||||||
|
Deleted Networks:
|
||||||
|
foo-1-day-ago
|
||||||
|
|
||||||
|
$ docker network ls
|
||||||
|
NETWORK ID NAME DRIVER SCOPE
|
||||||
|
7430df902d7a bridge bridge local
|
||||||
|
ab53663ed3c7 foo-1-min-ago bridge local
|
||||||
|
97b91972bc3b host host local
|
||||||
|
f949d337b1f5 none null local
|
||||||
|
```
|
||||||
|
|
||||||
## Related information
|
## Related information
|
||||||
|
|
||||||
* [network disconnect ](network_disconnect.md)
|
* [network disconnect ](network_disconnect.md)
|
||||||
|
|
|
@ -21,9 +21,10 @@ Usage: docker system prune [OPTIONS]
|
||||||
Delete unused data
|
Delete unused data
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-a, --all Remove all unused data not just dangling ones
|
-a, --all Remove all unused images not just dangling ones
|
||||||
-f, --force Do not prompt for confirmation
|
--filter filter Provide filter values (e.g. 'until=<timestamp>')
|
||||||
--help Print usage
|
-f, --force Do not prompt for confirmation
|
||||||
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
Remove all unused containers, volumes, networks and images (both dangling and unreferenced).
|
Remove all unused containers, volumes, networks and images (both dangling and unreferenced).
|
||||||
|
@ -64,6 +65,27 @@ deleted: sha256:3a88a5c81eb5c283e72db2dbc6d65cbfd8e80b6c89bb6e714cfaaa0eed99c548
|
||||||
Total reclaimed space: 13.5 MB
|
Total reclaimed space: 13.5 MB
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filtering
|
||||||
|
|
||||||
|
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||||
|
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||||
|
|
||||||
|
The currently supported filters are:
|
||||||
|
|
||||||
|
* until (`<timestamp>`) - only remove containers, images, and networks created before given timestamp
|
||||||
|
|
||||||
|
The `until` filter can be Unix timestamps, date formatted
|
||||||
|
timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
|
||||||
|
relative to the daemon machine’s time. Supported formats for date
|
||||||
|
formatted time stamps include RFC3339Nano, RFC3339, `2006-01-02T15:04:05`,
|
||||||
|
`2006-01-02T15:04:05.999999999`, `2006-01-02Z07:00`, and `2006-01-02`. The local
|
||||||
|
timezone on the daemon will be used if you do not provide either a `Z` or a
|
||||||
|
`+-00:00` timezone offset at the end of the timestamp. When providing Unix
|
||||||
|
timestamps enter seconds[.nanoseconds], where seconds is the number of seconds
|
||||||
|
that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap
|
||||||
|
seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||||
|
fraction of a second no more than nine digits long.
|
||||||
|
|
||||||
## Related information
|
## Related information
|
||||||
|
|
||||||
* [volume create](volume_create.md)
|
* [volume create](volume_create.md)
|
||||||
|
|
|
@ -5,6 +5,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/checker"
|
"github.com/docker/docker/integration-cli/checker"
|
||||||
"github.com/docker/docker/integration-cli/daemon"
|
"github.com/docker/docker/integration-cli/daemon"
|
||||||
|
@ -90,3 +91,23 @@ func (s *DockerDaemonSuite) TestPruneImageDangling(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), id)
|
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestPruneContainerUntil(c *check.C) {
|
||||||
|
out, _ := dockerCmd(c, "run", "-d", "busybox")
|
||||||
|
id1 := strings.TrimSpace(out)
|
||||||
|
c.Assert(waitExited(id1, 5*time.Second), checker.IsNil)
|
||||||
|
|
||||||
|
until := daemonUnixTime(c)
|
||||||
|
|
||||||
|
out, _ = dockerCmd(c, "run", "-d", "busybox")
|
||||||
|
id2 := strings.TrimSpace(out)
|
||||||
|
c.Assert(waitExited(id2, 5*time.Second), checker.IsNil)
|
||||||
|
|
||||||
|
out, _ = dockerCmd(c, "container", "prune", "--force", "--filter", "until="+until)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Contains, id1)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), id2)
|
||||||
|
|
||||||
|
out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc")
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), id1)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Contains, id2)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue