浏览代码

Merge pull request #31302 from dnephin/purge-orphaned-services

Add --prune to stack deploy
Sebastiaan van Stijn 8 年之前
父节点
当前提交
b0d1936d30

+ 24 - 0
cli/command/stack/deploy.go

@@ -3,8 +3,10 @@ package stack
 import (
 	"fmt"
 
+	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/cli/compose/convert"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
@@ -19,6 +21,7 @@ type deployOptions struct {
 	composefile      string
 	namespace        string
 	sendRegistryAuth bool
+	prune            bool
 }
 
 func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
@@ -39,6 +42,8 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
 	addBundlefileFlag(&opts.bundlefile, flags)
 	addComposefileFlag(&opts.composefile, flags)
 	addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
+	flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
+	flags.SetAnnotation("prune", "version", []string{"1.27"})
 	return cmd
 }
 
@@ -71,3 +76,22 @@ func checkDaemonIsSwarmManager(ctx context.Context, dockerCli *command.DockerCli
 	}
 	return nil
 }
+
+// pruneServices removes services that are no longer referenced in the source
+func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) bool {
+	client := dockerCli.Client()
+
+	oldServices, err := getServices(ctx, client, namespace.Name())
+	if err != nil {
+		fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
+		return true
+	}
+
+	pruneServices := []swarm.Service{}
+	for _, service := range oldServices {
+		if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
+			pruneServices = append(pruneServices, service)
+		}
+	}
+	return removeServices(ctx, dockerCli, pruneServices)
+}

+ 8 - 0
cli/command/stack/deploy_bundlefile.go

@@ -21,6 +21,14 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
 
 	namespace := convert.NewNamespace(opts.namespace)
 
+	if opts.prune {
+		services := map[string]struct{}{}
+		for service := range bundle.Services {
+			services[service] = struct{}{}
+		}
+		pruneServices(ctx, dockerCli, namespace, services)
+	}
+
 	networks := make(map[string]types.NetworkCreate)
 	for _, service := range bundle.Services {
 		for _, networkName := range service.Networks {

+ 8 - 1
cli/command/stack/deploy_composefile.go

@@ -52,8 +52,15 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
 
 	namespace := convert.NewNamespace(opts.namespace)
 
-	serviceNetworks := getServicesDeclaredNetworks(config.Services)
+	if opts.prune {
+		services := map[string]struct{}{}
+		for _, service := range config.Services {
+			services[service.Name] = struct{}{}
+		}
+		pruneServices(ctx, dockerCli, namespace, services)
+	}
 
+	serviceNetworks := getServicesDeclaredNetworks(config.Services)
 	networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks)
 	if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
 		return err

+ 54 - 0
cli/command/stack/deploy_test.go

@@ -0,0 +1,54 @@
+package stack
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/cli/compose/convert"
+	"github.com/docker/docker/cli/internal/test"
+	"github.com/docker/docker/client"
+	"github.com/docker/docker/pkg/testutil/assert"
+	"golang.org/x/net/context"
+)
+
+type fakeClient struct {
+	client.Client
+	serviceList []string
+	removedIDs  []string
+}
+
+func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
+	services := []swarm.Service{}
+	for _, name := range cli.serviceList {
+		services = append(services, swarm.Service{
+			ID: name,
+			Spec: swarm.ServiceSpec{
+				Annotations: swarm.Annotations{Name: name},
+			},
+		})
+	}
+	return services, nil
+}
+
+func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
+	cli.removedIDs = append(cli.removedIDs, serviceID)
+	return nil
+}
+
+func TestPruneServices(t *testing.T) {
+	ctx := context.Background()
+	namespace := convert.NewNamespace("foo")
+	services := map[string]struct{}{
+		"new":  {},
+		"keep": {},
+	}
+	client := &fakeClient{serviceList: []string{"foo_keep", "foo_remove"}}
+	dockerCli := test.NewFakeCli(client, &bytes.Buffer{})
+	dockerCli.SetErr(&bytes.Buffer{})
+
+	pruneServices(ctx, dockerCli, namespace, services)
+
+	assert.DeepEqual(t, client.removedIDs, []string{"foo_remove"})
+}

+ 3 - 3
cli/command/stack/remove.go

@@ -68,7 +68,7 @@ func runRemove(dockerCli *command.DockerCli, opts removeOptions) error {
 
 func removeServices(
 	ctx context.Context,
-	dockerCli *command.DockerCli,
+	dockerCli command.Cli,
 	services []swarm.Service,
 ) bool {
 	var err error
@@ -83,7 +83,7 @@ func removeServices(
 
 func removeNetworks(
 	ctx context.Context,
-	dockerCli *command.DockerCli,
+	dockerCli command.Cli,
 	networks []types.NetworkResource,
 ) bool {
 	var err error
@@ -98,7 +98,7 @@ func removeNetworks(
 
 func removeSecrets(
 	ctx context.Context,
-	dockerCli *command.DockerCli,
+	dockerCli command.Cli,
 	secrets []swarm.Secret,
 ) bool {
 	var err error

+ 6 - 0
cli/compose/convert/compose.go

@@ -2,6 +2,7 @@ package convert
 
 import (
 	"io/ioutil"
+	"strings"
 
 	"github.com/docker/docker/api/types"
 	networktypes "github.com/docker/docker/api/types/network"
@@ -24,6 +25,11 @@ func (n Namespace) Scope(name string) string {
 	return n.name + "_" + name
 }
 
+// Descope returns the name without the namespace prefix
+func (n Namespace) Descope(name string) string {
+	return strings.TrimPrefix(name, n.name+"_")
+}
+
 // Name returns the name of the namespace
 func (n Namespace) Name() string {
 	return n.name

+ 1 - 1
cli/internal/test/cli.go

@@ -35,7 +35,7 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
 	c.in = in
 }
 
-// SetErr sets the standard error stream th cli should write on
+// SetErr sets the stderr stream for the cli to the specified io.Writer
 func (c *FakeCli) SetErr(err io.Writer) {
 	c.err = err
 }

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

@@ -30,6 +30,7 @@ Options:
       --bundle-file string    Path to a Distributed Application Bundle file
       --compose-file string   Path to a Compose file
       --help                  Print usage
+      --prune                 Prune services that are no longer referenced
       --with-registry-auth    Send registry authentication details to Swarm agents
 ```
 

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

@@ -27,6 +27,7 @@ Options:
       --bundle-file string    Path to a Distributed Application Bundle file
   -c, --compose-file string   Path to a Compose file
       --help                  Print usage
+      --prune                 Prune services that are no longer referenced
       --with-registry-auth    Send registry authentication details to Swarm agents
 ```