Skip to content

Commit

Permalink
Merge pull request #31302 from dnephin/purge-orphaned-services
Browse files Browse the repository at this point in the history
Add --prune to stack deploy
  • Loading branch information
thaJeztah committed Mar 15, 2017
2 parents 0b9b790 + 644fd80 commit b0d1936
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 5 deletions.
24 changes: 24 additions & 0 deletions cli/command/stack/deploy.go
Expand Up @@ -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"
Expand All @@ -19,6 +21,7 @@ type deployOptions struct {
composefile string
namespace string
sendRegistryAuth bool
prune bool
}

func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
Expand All @@ -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
}

Expand Down Expand Up @@ -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 changes: 8 additions & 0 deletions cli/command/stack/deploy_bundlefile.go
Expand Up @@ -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 {
Expand Down
9 changes: 8 additions & 1 deletion cli/command/stack/deploy_composefile.go
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions 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"})
}
6 changes: 3 additions & 3 deletions cli/command/stack/remove.go
Expand Up @@ -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
Expand All @@ -83,7 +83,7 @@ func removeServices(

func removeNetworks(
ctx context.Context,
dockerCli *command.DockerCli,
dockerCli command.Cli,
networks []types.NetworkResource,
) bool {
var err error
Expand All @@ -98,7 +98,7 @@ func removeNetworks(

func removeSecrets(
ctx context.Context,
dockerCli *command.DockerCli,
dockerCli command.Cli,
secrets []swarm.Secret,
) bool {
var err error
Expand Down
6 changes: 6 additions & 0 deletions cli/compose/convert/compose.go
Expand Up @@ -2,6 +2,7 @@ package convert

import (
"io/ioutil"
"strings"

"github.com/docker/docker/api/types"
networktypes "github.com/docker/docker/api/types/network"
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cli/internal/test/cli.go
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions docs/reference/commandline/deploy.md
Expand Up @@ -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
```

Expand Down
1 change: 1 addition & 0 deletions docs/reference/commandline/stack_deploy.md
Expand Up @@ -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
```

Expand Down

0 comments on commit b0d1936

Please sign in to comment.