mirror of https://github.com/docker/cli.git
Add --prune to stack deploy.
Add to command line reference. Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
5ce6afc459
commit
b1a98b55af
|
@ -3,8 +3,10 @@ package stack
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
|
"github.com/docker/docker/cli/compose/convert"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -19,6 +21,7 @@ type deployOptions struct {
|
||||||
composefile string
|
composefile string
|
||||||
namespace string
|
namespace string
|
||||||
sendRegistryAuth bool
|
sendRegistryAuth bool
|
||||||
|
prune bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
|
@ -39,6 +42,8 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
addBundlefileFlag(&opts.bundlefile, flags)
|
addBundlefileFlag(&opts.bundlefile, flags)
|
||||||
addComposefileFlag(&opts.composefile, flags)
|
addComposefileFlag(&opts.composefile, flags)
|
||||||
addRegistryAuthFlag(&opts.sendRegistryAuth, 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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,3 +76,22 @@ func checkDaemonIsSwarmManager(ctx context.Context, dockerCli *command.DockerCli
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
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)
|
networks := make(map[string]types.NetworkCreate)
|
||||||
for _, service := range bundle.Services {
|
for _, service := range bundle.Services {
|
||||||
for _, networkName := range service.Networks {
|
for _, networkName := range service.Networks {
|
||||||
|
|
|
@ -52,8 +52,15 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
|
||||||
|
|
||||||
namespace := convert.NewNamespace(opts.namespace)
|
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)
|
networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks)
|
||||||
if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
|
if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -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(
|
func removeServices(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dockerCli *command.DockerCli,
|
dockerCli command.Cli,
|
||||||
services []swarm.Service,
|
services []swarm.Service,
|
||||||
) bool {
|
) bool {
|
||||||
var err error
|
var err error
|
||||||
|
@ -83,7 +83,7 @@ func removeServices(
|
||||||
|
|
||||||
func removeNetworks(
|
func removeNetworks(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dockerCli *command.DockerCli,
|
dockerCli command.Cli,
|
||||||
networks []types.NetworkResource,
|
networks []types.NetworkResource,
|
||||||
) bool {
|
) bool {
|
||||||
var err error
|
var err error
|
||||||
|
@ -98,7 +98,7 @@ func removeNetworks(
|
||||||
|
|
||||||
func removeSecrets(
|
func removeSecrets(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dockerCli *command.DockerCli,
|
dockerCli command.Cli,
|
||||||
secrets []swarm.Secret,
|
secrets []swarm.Secret,
|
||||||
) bool {
|
) bool {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -2,6 +2,7 @@ package convert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
|
@ -24,6 +25,11 @@ func (n Namespace) Scope(name string) string {
|
||||||
return n.name + "_" + name
|
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
|
// Name returns the name of the namespace
|
||||||
func (n Namespace) Name() string {
|
func (n Namespace) Name() string {
|
||||||
return n.name
|
return n.name
|
||||||
|
|
|
@ -35,7 +35,7 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
|
||||||
c.in = in
|
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) {
|
func (c *FakeCli) SetErr(err io.Writer) {
|
||||||
c.err = err
|
c.err = err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue