Add --prune to stack deploy.

Add to command line reference.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2017-02-22 15:43:13 -05:00
parent 5ce6afc459
commit b1a98b55af
7 changed files with 104 additions and 5 deletions

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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"})
}

View File

@ -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

View File

@ -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

View File

@ -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
}