Browse Source

Merge pull request #27525 from AkihiroSuda/prune-network-cluster

add `docker network prune`
Vincent Demeester 8 năm trước cách đây
mục cha
commit
11cd64f0dc

+ 1 - 0
api/server/router/network/backend.go

@@ -17,4 +17,5 @@ type Backend interface {
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
 	DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
 	DeleteNetwork(name string) error
 	DeleteNetwork(name string) error
+	NetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error)
 }
 }

+ 1 - 0
api/server/router/network/network.go

@@ -37,6 +37,7 @@ func (r *networkRouter) initRoutes() {
 		router.NewPostRoute("/networks/create", r.postNetworkCreate),
 		router.NewPostRoute("/networks/create", r.postNetworkCreate),
 		router.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
 		router.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
 		router.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
 		router.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
+		router.NewPostRoute("/networks/prune", r.postNetworksPrune),
 		// DELETE
 		// DELETE
 		router.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
 		router.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
 	}
 	}

+ 21 - 0
api/server/router/network/network_routes.go

@@ -274,3 +274,24 @@ func buildEndpointResource(id string, name string, info libnetwork.EndpointInfo)
 	}
 	}
 	return er
 	return er
 }
 }
+
+func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	if err := httputils.CheckForJSON(r); err != nil {
+		return err
+	}
+
+	var cfg types.NetworksPruneConfig
+	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
+		return err
+	}
+
+	pruneReport, err := n.backend.NetworksPrune(&cfg)
+	if err != nil {
+		return err
+	}
+	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
+}

+ 11 - 0
api/types/types.go

@@ -522,6 +522,11 @@ type ContainersPruneConfig struct {
 type VolumesPruneConfig struct {
 type VolumesPruneConfig struct {
 }
 }
 
 
+// NetworksPruneConfig contains the configuration for Remote API:
+// POST "/networks/prune"
+type NetworksPruneConfig struct {
+}
+
 // ContainersPruneReport contains the response for Remote API:
 // ContainersPruneReport contains the response for Remote API:
 // POST "/containers/prune"
 // POST "/containers/prune"
 type ContainersPruneReport struct {
 type ContainersPruneReport struct {
@@ -542,3 +547,9 @@ type ImagesPruneReport struct {
 	ImagesDeleted  []ImageDelete
 	ImagesDeleted  []ImageDelete
 	SpaceReclaimed uint64
 	SpaceReclaimed uint64
 }
 }
+
+// NetworksPruneReport contains the response for Remote API:
+// POST "/networks/prune"
+type NetworksPruneReport struct {
+	NetworksDeleted []string
+}

+ 1 - 0
cli/command/network/cmd.go

@@ -26,6 +26,7 @@ func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
 		newInspectCommand(dockerCli),
 		newInspectCommand(dockerCli),
 		newListCommand(dockerCli),
 		newListCommand(dockerCli),
 		newRemoveCommand(dockerCli),
 		newRemoveCommand(dockerCli),
+		NewPruneCommand(dockerCli),
 	)
 	)
 	return cmd
 	return cmd
 }
 }

+ 72 - 0
cli/command/network/prune.go

@@ -0,0 +1,72 @@
+package network
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/cli/command"
+	"github.com/spf13/cobra"
+)
+
+type pruneOptions struct {
+	force bool
+}
+
+// NewPruneCommand returns a new cobra prune command for networks
+func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
+	var opts pruneOptions
+
+	cmd := &cobra.Command{
+		Use:   "prune [OPTIONS]",
+		Short: "Remove all unused networks",
+		Args:  cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			output, err := runPrune(dockerCli, opts)
+			if err != nil {
+				return err
+			}
+			if output != "" {
+				fmt.Fprintln(dockerCli.Out(), output)
+			}
+			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 networks not used by at least one container.
+Are you sure you want to continue?`
+
+func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, err error) {
+	if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
+		return
+	}
+
+	report, err := dockerCli.Client().NetworksPrune(context.Background(), types.NetworksPruneConfig{})
+	if err != nil {
+		return
+	}
+
+	if len(report.NetworksDeleted) > 0 {
+		output = "Deleted Networks:\n"
+		for _, id := range report.NetworksDeleted {
+			output += id + "\n"
+		}
+	}
+
+	return
+}
+
+// RunPrune calls the Network Prune API
+// This returns the amount of space reclaimed and a detailed output string
+func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
+	output, err := runPrune(dockerCli, pruneOptions{force: true})
+	return 0, output, err
+}

+ 11 - 0
cli/command/prune/prune.go

@@ -4,6 +4,7 @@ import (
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/container"
 	"github.com/docker/docker/cli/command/container"
 	"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/volume"
 	"github.com/docker/docker/cli/command/volume"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
@@ -23,6 +24,11 @@ func NewImagePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
 	return image.NewPruneCommand(dockerCli)
 	return image.NewPruneCommand(dockerCli)
 }
 }
 
 
+// NewNetworkPruneCommand returns a cobra prune command for Networks
+func NewNetworkPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
+	return network.NewPruneCommand(dockerCli)
+}
+
 // 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) (uint64, string, error) {
 	return container.RunPrune(dockerCli)
 	return container.RunPrune(dockerCli)
@@ -37,3 +43,8 @@ func RunVolumePrune(dockerCli *command.DockerCli) (uint64, string, error) {
 func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
 func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
 	return image.RunPrune(dockerCli, all)
 	return image.RunPrune(dockerCli, all)
 }
 }
+
+// RunNetworkPrune executes a prune command for networks
+func RunNetworkPrune(dockerCli *command.DockerCli) (uint64, string, error) {
+	return network.RunPrune(dockerCli)
+}

+ 4 - 2
cli/command/system/prune.go

@@ -39,6 +39,7 @@ const (
 	warning = `WARNING! This will remove:
 	warning = `WARNING! This will remove:
 	- all stopped containers
 	- all stopped containers
 	- all volumes not used by at least one container
 	- all volumes not used by at least one container
+	- all networks not used by at least one container
 	%s
 	%s
 Are you sure you want to continue?`
 Are you sure you want to continue?`
 
 
@@ -64,13 +65,14 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
 	for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
 	for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
 		prune.RunContainerPrune,
 		prune.RunContainerPrune,
 		prune.RunVolumePrune,
 		prune.RunVolumePrune,
+		prune.RunNetworkPrune,
 	} {
 	} {
 		spc, output, err := pruneFn(dockerCli)
 		spc, output, err := pruneFn(dockerCli)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		if spc > 0 {
-			spaceReclaimed += spc
+		spaceReclaimed += spc
+		if output != "" {
 			fmt.Fprintln(dockerCli.Out(), output)
 			fmt.Fprintln(dockerCli.Out(), output)
 		}
 		}
 	}
 	}

+ 1 - 0
client/interface.go

@@ -91,6 +91,7 @@ type NetworkAPIClient interface {
 	NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
 	NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
 	NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
 	NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
 	NetworkRemove(ctx context.Context, networkID string) error
 	NetworkRemove(ctx context.Context, networkID string) error
+	NetworksPrune(ctx context.Context, cfg types.NetworksPruneConfig) (types.NetworksPruneReport, error)
 }
 }
 
 
 // NodeAPIClient defines API client methods for the nodes
 // NodeAPIClient defines API client methods for the nodes

+ 26 - 0
client/network_prune.go

@@ -0,0 +1,26 @@
+package client
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/docker/docker/api/types"
+	"golang.org/x/net/context"
+)
+
+// NetworksPrune requests the daemon to delete unused networks
+func (cli *Client) NetworksPrune(ctx context.Context, cfg types.NetworksPruneConfig) (types.NetworksPruneReport, error) {
+	var report types.NetworksPruneReport
+
+	serverResp, err := cli.post(ctx, "/networks/prune", nil, cfg, nil)
+	if err != nil {
+		return report, err
+	}
+	defer ensureReaderClosed(serverResp)
+
+	if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
+		return report, fmt.Errorf("Error retrieving network prune report: %v", err)
+	}
+
+	return report, nil
+}

+ 1 - 0
cmd/dockerd/daemon.go

@@ -279,6 +279,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
 
 
 	// initMiddlewares needs cli.d to be populated. Dont change this init order.
 	// initMiddlewares needs cli.d to be populated. Dont change this init order.
 	cli.initMiddlewares(api, serverConfig)
 	cli.initMiddlewares(api, serverConfig)
+	d.SetCluster(c)
 	initRouter(api, d, c)
 	initRouter(api, d, c)
 
 
 	cli.setupConfigReloadTrap()
 	cli.setupConfigReloadTrap()

+ 12 - 0
daemon/cluster.go

@@ -0,0 +1,12 @@
+package daemon
+
+import (
+	apitypes "github.com/docker/docker/api/types"
+)
+
+// Cluster is the interface for github.com/docker/docker/daemon/cluster.(*Cluster).
+type Cluster interface {
+	GetNetwork(input string) (apitypes.NetworkResource, error)
+	GetNetworks() ([]apitypes.NetworkResource, error)
+	RemoveNetwork(input string) error
+}

+ 11 - 0
daemon/daemon.go

@@ -102,6 +102,7 @@ type Daemon struct {
 	containerdRemote          libcontainerd.Remote
 	containerdRemote          libcontainerd.Remote
 	defaultIsolation          containertypes.Isolation // Default isolation mode on Windows
 	defaultIsolation          containertypes.Isolation // Default isolation mode on Windows
 	clusterProvider           cluster.Provider
 	clusterProvider           cluster.Provider
+	cluster                   Cluster
 }
 }
 
 
 // HasExperimental returns whether the experimental features of the daemon are enabled or not
 // HasExperimental returns whether the experimental features of the daemon are enabled or not
@@ -1234,3 +1235,13 @@ func copyBlkioEntry(entries []*containerd.BlkioStatsEntry) []types.BlkioStatEntr
 	}
 	}
 	return out
 	return out
 }
 }
+
+// GetCluster returns the cluster
+func (daemon *Daemon) GetCluster() Cluster {
+	return daemon.cluster
+}
+
+// SetCluster sets the cluster
+func (daemon *Daemon) SetCluster(cluster Cluster) {
+	daemon.cluster = cluster
+}

+ 73 - 0
daemon/prune.go

@@ -1,6 +1,8 @@
 package daemon
 package daemon
 
 
 import (
 import (
+	"regexp"
+
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/digest"
 	"github.com/docker/distribution/digest"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -8,7 +10,9 @@ import (
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/pkg/directory"
 	"github.com/docker/docker/pkg/directory"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
+	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
+	"github.com/docker/libnetwork"
 )
 )
 
 
 // ContainersPrune removes unused containers
 // ContainersPrune removes unused containers
@@ -150,3 +154,72 @@ func (daemon *Daemon) ImagesPrune(config *types.ImagesPruneConfig) (*types.Image
 
 
 	return rep, nil
 	return rep, nil
 }
 }
+
+// localNetworksPrune removes unused local networks
+func (daemon *Daemon) localNetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) {
+	rep := &types.NetworksPruneReport{}
+	var err error
+	// When the function returns true, the walk will stop.
+	l := func(nw libnetwork.Network) bool {
+		nwName := nw.Name()
+		predefined := runconfig.IsPreDefinedNetwork(nwName)
+		if !predefined && len(nw.Endpoints()) == 0 {
+			if err = daemon.DeleteNetwork(nw.ID()); err != nil {
+				logrus.Warnf("could not remove network %s: %v", nwName, err)
+				return false
+			}
+			rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
+		}
+		return false
+	}
+	daemon.netController.WalkNetworks(l)
+	return rep, err
+}
+
+// clusterNetworksPrune removes unused cluster networks
+func (daemon *Daemon) clusterNetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) {
+	rep := &types.NetworksPruneReport{}
+	cluster := daemon.GetCluster()
+	networks, err := cluster.GetNetworks()
+	if err != nil {
+		return rep, err
+	}
+	networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
+	for _, nw := range networks {
+		if nw.Name == "ingress" {
+			continue
+		}
+		// https://github.com/docker/docker/issues/24186
+		// `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
+		err = cluster.RemoveNetwork(nw.ID)
+		if err != nil {
+			// we can safely ignore the "network .. is in use" error
+			match := networkIsInUse.FindStringSubmatch(err.Error())
+			if len(match) != 2 || match[1] != nw.ID {
+				logrus.Warnf("could not remove network %s: %v", nw.Name, err)
+			}
+			continue
+		}
+		rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
+	}
+	return rep, nil
+}
+
+// NetworksPrune removes unused networks
+func (daemon *Daemon) NetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) {
+	rep := &types.NetworksPruneReport{}
+	clusterRep, err := daemon.clusterNetworksPrune(config)
+	if err != nil {
+		logrus.Warnf("could not remove cluster networks: %v", err)
+	} else {
+		rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
+	}
+	localRep, err := daemon.localNetworksPrune(config)
+	if err != nil {
+		logrus.Warnf("could not remove local networks: %v", err)
+	} else {
+		rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
+	}
+	return rep, err
+}

+ 2 - 0
docs/reference/api/docker_remote_api.md

@@ -157,8 +157,10 @@ This section lists each version from latest to oldest.  Each listing includes a
 * `POST /containers/prune` prunes stopped containers.
 * `POST /containers/prune` prunes stopped containers.
 * `POST /images/prune` prunes unused images.
 * `POST /images/prune` prunes unused images.
 * `POST /volumes/prune` prunes unused volumes.
 * `POST /volumes/prune` prunes unused volumes.
+* `POST /networks/prune` prunes unused networks.
 * Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`).
 * Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`).
 
 
+
 ### v1.24 API changes
 ### v1.24 API changes
 
 
 [Docker Remote API v1.24](docker_remote_api_v1.24.md) documentation
 [Docker Remote API v1.24](docker_remote_api_v1.24.md) documentation

+ 30 - 0
docs/reference/api/docker_remote_api_v1.25.md

@@ -3881,6 +3881,36 @@ Instruct the driver to remove the network (`id`).
 -   **404** - no such network
 -   **404** - no such network
 -   **500** - server error
 -   **500** - server error
 
 
+### Prune unused networks
+
+`POST /networks/prune`
+
+Delete unused networks
+
+**Example request**:
+
+    POST /networks/prune HTTP/1.1
+    Content-Type: application/json
+
+    {
+    }
+
+**Example response**:
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+    {
+        "NetworksDeleted": [
+            "n1"
+        ],
+    }
+
+**Status codes**:
+
+-   **200** – no error
+-   **500** – server error
+
 ## 3.6 Plugins
 ## 3.6 Plugins
 
 
 ### List plugins
 ### List plugins

+ 1 - 0
docs/reference/commandline/container_prune.md

@@ -43,4 +43,5 @@ Total reclaimed space: 212 B
 * [system df](system_df.md)
 * [system df](system_df.md)
 * [volume prune](volume_prune.md)
 * [volume prune](volume_prune.md)
 * [image prune](image_prune.md)
 * [image prune](image_prune.md)
+* [network prune](network_prune.md)
 * [system prune](system_prune.md)
 * [system prune](system_prune.md)

+ 1 - 0
docs/reference/commandline/image_prune.md

@@ -67,4 +67,5 @@ Total reclaimed space: 16.43 MB
 * [system df](system_df.md)
 * [system df](system_df.md)
 * [container prune](container_prune.md)
 * [container prune](container_prune.md)
 * [volume prune](volume_prune.md)
 * [volume prune](volume_prune.md)
+* [network prune](network_prune.md)
 * [system prune](system_prune.md)
 * [system prune](system_prune.md)

+ 1 - 0
docs/reference/commandline/network_connect.md

@@ -98,5 +98,6 @@ You can connect a container to one or more networks. The networks need not be th
 * [network disconnect](network_disconnect.md)
 * [network disconnect](network_disconnect.md)
 * [network ls](network_ls.md)
 * [network ls](network_ls.md)
 * [network rm](network_rm.md)
 * [network rm](network_rm.md)
+* [network prune](network_prune.md)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Work with networks](https://docs.docker.com/engine/userguide/networking/work-with-networks/)
 * [Work with networks](https://docs.docker.com/engine/userguide/networking/work-with-networks/)

+ 1 - 0
docs/reference/commandline/network_create.md

@@ -197,4 +197,5 @@ to create an externally isolated `overlay` network, you can specify the
 * [network disconnect](network_disconnect.md)
 * [network disconnect](network_disconnect.md)
 * [network ls](network_ls.md)
 * [network ls](network_ls.md)
 * [network rm](network_rm.md)
 * [network rm](network_rm.md)
+* [network prune](network_prune.md)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

+ 1 - 0
docs/reference/commandline/network_disconnect.md

@@ -39,4 +39,5 @@ Disconnects a container from a network. The container must be running to disconn
 * [network create](network_create.md)
 * [network create](network_create.md)
 * [network ls](network_ls.md)
 * [network ls](network_ls.md)
 * [network rm](network_rm.md)
 * [network rm](network_rm.md)
+* [network prune](network_prune.md)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

+ 1 - 0
docs/reference/commandline/network_inspect.md

@@ -131,4 +131,5 @@ $ docker network inspect simple-network
 * [network create](network_create.md)
 * [network create](network_create.md)
 * [network ls](network_ls.md)
 * [network ls](network_ls.md)
 * [network rm](network_rm.md)
 * [network rm](network_rm.md)
+* [network prune](network_prune.md)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

+ 1 - 0
docs/reference/commandline/network_ls.md

@@ -214,4 +214,5 @@ d1584f8dc718: host
 * [network create](network_create.md)
 * [network create](network_create.md)
 * [network inspect](network_inspect.md)
 * [network inspect](network_inspect.md)
 * [network rm](network_rm.md)
 * [network rm](network_rm.md)
+* [network prune](network_prune.md)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

+ 45 - 0
docs/reference/commandline/network_prune.md

@@ -0,0 +1,45 @@
+---
+title: "network prune"
+description: "Remove unused networks"
+keywords: [network, prune, delete]
+---
+
+# network prune
+
+```markdown
+Usage:	docker network prune [OPTIONS]
+
+Remove all unused networks
+
+Options:
+  -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.
+
+Example output:
+
+```bash
+$ docker network prune
+WARNING! This will remove all networks not used by at least one container.
+Are you sure you want to continue? [y/N] y
+Deleted Networks:
+n1
+n2
+```
+
+## Related information
+
+* [network disconnect ](network_disconnect.md)
+* [network connect](network_connect.md)
+* [network create](network_create.md)
+* [network ls](network_ls.md)
+* [network inspect](network_inspect.md)
+* [network rm](network_rm.md)
+* [Understand Docker container networks](../../userguide/networking/index.md)
+* [system df](system_df.md)
+* [container prune](container_prune.md)
+* [image prune](image_prune.md)
+* [volume prune](volume_prune.md)
+* [system prune](system_prune.md)

+ 1 - 0
docs/reference/commandline/network_rm.md

@@ -55,4 +55,5 @@ deletion.
 * [network create](network_create.md)
 * [network create](network_create.md)
 * [network ls](network_ls.md)
 * [network ls](network_ls.md)
 * [network inspect](network_inspect.md)
 * [network inspect](network_inspect.md)
+* [network prune](network_prune.md)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
 * [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

+ 3 - 0
docs/reference/commandline/system_df.md

@@ -66,8 +66,11 @@ my-named-vol                                                       0
 * `UNIQUE SIZE` is the amount of space that is only used by a given image
 * `UNIQUE SIZE` is the amount of space that is only used by a given image
 * `SIZE` is the virtual size of the image, it is the sum of `SHARED SIZE` and `UNIQUE SIZE`
 * `SIZE` is the virtual size of the image, it is the sum of `SHARED SIZE` and `UNIQUE SIZE`
 
 
+Note that network information is not shown because it doesn't consume the disk space.
+
 ## Related Information
 ## Related Information
 * [system prune](system_prune.md)
 * [system prune](system_prune.md)
 * [container prune](container_prune.md)
 * [container prune](container_prune.md)
 * [volume prune](volume_prune.md)
 * [volume prune](volume_prune.md)
 * [image prune](image_prune.md)
 * [image prune](image_prune.md)
+* [network prune](network_prune.md)

+ 3 - 1
docs/reference/commandline/system_prune.md

@@ -26,7 +26,7 @@ Options:
       --help    Print usage
       --help    Print usage
 ```
 ```
 
 
-Remove all unused containers, volumes and images (both dangling and unreferenced).
+Remove all unused containers, volumes, networks and images (both dangling and unreferenced).
 
 
 Example output:
 Example output:
 
 
@@ -35,6 +35,7 @@ $ docker system prune -a
 WARNING! This will remove:
 WARNING! This will remove:
 	- all stopped containers
 	- all stopped containers
 	- all volumes not used by at least one container
 	- all volumes not used by at least one container
+	- all networks not used by at least one container
 	- all images without at least one container associated to them
 	- all images without at least one container associated to them
 Are you sure you want to continue? [y/N] y
 Are you sure you want to continue? [y/N] y
 Deleted Containers:
 Deleted Containers:
@@ -74,4 +75,5 @@ Total reclaimed space: 13.5 MB
 * [system df](system_df.md)
 * [system df](system_df.md)
 * [container prune](container_prune.md)
 * [container prune](container_prune.md)
 * [image prune](image_prune.md)
 * [image prune](image_prune.md)
+* [network prune](network_prune.md)
 * [system prune](system_prune.md)
 * [system prune](system_prune.md)

+ 1 - 0
docs/reference/commandline/volume_prune.md

@@ -50,4 +50,5 @@ Total reclaimed space: 36 B
 * [system df](system_df.md)
 * [system df](system_df.md)
 * [container prune](container_prune.md)
 * [container prune](container_prune.md)
 * [image prune](image_prune.md)
 * [image prune](image_prune.md)
+* [network prune](network_prune.md)
 * [system prune](system_prune.md)
 * [system prune](system_prune.md)

+ 61 - 0
integration-cli/docker_cli_prune_unix_test.go

@@ -0,0 +1,61 @@
+// +build !windows
+
+package main
+
+import (
+	"strconv"
+	"strings"
+
+	"github.com/docker/docker/pkg/integration/checker"
+	"github.com/go-check/check"
+)
+
+func pruneNetworkAndVerify(c *check.C, d *SwarmDaemon, kept, pruned []string) {
+	_, err := d.Cmd("network", "prune", "--force")
+	c.Assert(err, checker.IsNil)
+	out, err := d.Cmd("network", "ls", "--format", "{{.Name}}")
+	c.Assert(err, checker.IsNil)
+	for _, s := range kept {
+		c.Assert(out, checker.Contains, s)
+	}
+	for _, s := range pruned {
+		c.Assert(out, checker.Not(checker.Contains), s)
+	}
+}
+
+func (s *DockerSwarmSuite) TestPruneNetwork(c *check.C) {
+	d := s.AddDaemon(c, true, true)
+	_, err := d.Cmd("network", "create", "n1") // used by container (testprune)
+	c.Assert(err, checker.IsNil)
+	_, err = d.Cmd("network", "create", "n2")
+	c.Assert(err, checker.IsNil)
+	_, err = d.Cmd("network", "create", "n3", "--driver", "overlay") // used by service (testprunesvc)
+	c.Assert(err, checker.IsNil)
+	_, err = d.Cmd("network", "create", "n4", "--driver", "overlay")
+	c.Assert(err, checker.IsNil)
+
+	cName := "testprune"
+	_, err = d.Cmd("run", "-d", "--name", cName, "--net", "n1", "busybox", "top")
+	c.Assert(err, checker.IsNil)
+
+	serviceName := "testprunesvc"
+	replicas := 1
+	out, err := d.Cmd("service", "create", "--name", serviceName,
+		"--replicas", strconv.Itoa(replicas),
+		"--network", "n3",
+		"busybox", "top")
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
+	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, replicas+1)
+
+	// prune and verify
+	pruneNetworkAndVerify(c, d, []string{"n1", "n3"}, []string{"n2", "n4"})
+
+	// remove containers, then prune and verify again
+	_, err = d.Cmd("rm", "-f", cName)
+	c.Assert(err, checker.IsNil)
+	_, err = d.Cmd("service", "rm", serviceName)
+	c.Assert(err, checker.IsNil)
+	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 0)
+	pruneNetworkAndVerify(c, d, []string{}, []string{"n1", "n3"})
+}