mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add --prune to stack deploy.
Add to command line reference. Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
1a24abe42d
commit
644fd804fc
9 changed files with 106 additions and 5 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
cli/command/stack/deploy_test.go
Normal file
54
cli/command/stack/deploy_test.go
Normal 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"})
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in a new issue