Add to command line reference.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -3,8 +3,10 @@ package stack |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
|
| 6 |
+ "github.com/docker/docker/api/types/swarm" |
|
| 6 | 7 |
"github.com/docker/docker/cli" |
| 7 | 8 |
"github.com/docker/docker/cli/command" |
| 9 |
+ "github.com/docker/docker/cli/compose/convert" |
|
| 8 | 10 |
"github.com/pkg/errors" |
| 9 | 11 |
"github.com/spf13/cobra" |
| 10 | 12 |
"golang.org/x/net/context" |
| ... | ... |
@@ -19,6 +21,7 @@ type deployOptions struct {
|
| 19 | 19 |
composefile string |
| 20 | 20 |
namespace string |
| 21 | 21 |
sendRegistryAuth bool |
| 22 |
+ prune bool |
|
| 22 | 23 |
} |
| 23 | 24 |
|
| 24 | 25 |
func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| ... | ... |
@@ -39,6 +42,8 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 39 | 39 |
addBundlefileFlag(&opts.bundlefile, flags) |
| 40 | 40 |
addComposefileFlag(&opts.composefile, flags) |
| 41 | 41 |
addRegistryAuthFlag(&opts.sendRegistryAuth, flags) |
| 42 |
+ flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced") |
|
| 43 |
+ flags.SetAnnotation("prune", "version", []string{"1.27"})
|
|
| 42 | 44 |
return cmd |
| 43 | 45 |
} |
| 44 | 46 |
|
| ... | ... |
@@ -71,3 +76,22 @@ func checkDaemonIsSwarmManager(ctx context.Context, dockerCli *command.DockerCli |
| 71 | 71 |
} |
| 72 | 72 |
return nil |
| 73 | 73 |
} |
| 74 |
+ |
|
| 75 |
+// pruneServices removes services that are no longer referenced in the source |
|
| 76 |
+func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) bool {
|
|
| 77 |
+ client := dockerCli.Client() |
|
| 78 |
+ |
|
| 79 |
+ oldServices, err := getServices(ctx, client, namespace.Name()) |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err) |
|
| 82 |
+ return true |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ pruneServices := []swarm.Service{}
|
|
| 86 |
+ for _, service := range oldServices {
|
|
| 87 |
+ if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
|
|
| 88 |
+ pruneServices = append(pruneServices, service) |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ return removeServices(ctx, dockerCli, pruneServices) |
|
| 92 |
+} |
| ... | ... |
@@ -21,6 +21,14 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy |
| 21 | 21 |
|
| 22 | 22 |
namespace := convert.NewNamespace(opts.namespace) |
| 23 | 23 |
|
| 24 |
+ if opts.prune {
|
|
| 25 |
+ services := map[string]struct{}{}
|
|
| 26 |
+ for service := range bundle.Services {
|
|
| 27 |
+ services[service] = struct{}{}
|
|
| 28 |
+ } |
|
| 29 |
+ pruneServices(ctx, dockerCli, namespace, services) |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 24 | 32 |
networks := make(map[string]types.NetworkCreate) |
| 25 | 33 |
for _, service := range bundle.Services {
|
| 26 | 34 |
for _, networkName := range service.Networks {
|
| ... | ... |
@@ -52,8 +52,15 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo |
| 52 | 52 |
|
| 53 | 53 |
namespace := convert.NewNamespace(opts.namespace) |
| 54 | 54 |
|
| 55 |
- serviceNetworks := getServicesDeclaredNetworks(config.Services) |
|
| 55 |
+ if opts.prune {
|
|
| 56 |
+ services := map[string]struct{}{}
|
|
| 57 |
+ for _, service := range config.Services {
|
|
| 58 |
+ services[service.Name] = struct{}{}
|
|
| 59 |
+ } |
|
| 60 |
+ pruneServices(ctx, dockerCli, namespace, services) |
|
| 61 |
+ } |
|
| 56 | 62 |
|
| 63 |
+ serviceNetworks := getServicesDeclaredNetworks(config.Services) |
|
| 57 | 64 |
networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks) |
| 58 | 65 |
if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
|
| 59 | 66 |
return err |
| 60 | 67 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,54 @@ |
| 0 |
+package stack |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 |
+ "github.com/docker/docker/api/types/swarm" |
|
| 8 |
+ "github.com/docker/docker/cli/compose/convert" |
|
| 9 |
+ "github.com/docker/docker/cli/internal/test" |
|
| 10 |
+ "github.com/docker/docker/client" |
|
| 11 |
+ "github.com/docker/docker/pkg/testutil/assert" |
|
| 12 |
+ "golang.org/x/net/context" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+type fakeClient struct {
|
|
| 16 |
+ client.Client |
|
| 17 |
+ serviceList []string |
|
| 18 |
+ removedIDs []string |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
|
| 22 |
+ services := []swarm.Service{}
|
|
| 23 |
+ for _, name := range cli.serviceList {
|
|
| 24 |
+ services = append(services, swarm.Service{
|
|
| 25 |
+ ID: name, |
|
| 26 |
+ Spec: swarm.ServiceSpec{
|
|
| 27 |
+ Annotations: swarm.Annotations{Name: name},
|
|
| 28 |
+ }, |
|
| 29 |
+ }) |
|
| 30 |
+ } |
|
| 31 |
+ return services, nil |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
|
|
| 35 |
+ cli.removedIDs = append(cli.removedIDs, serviceID) |
|
| 36 |
+ return nil |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func TestPruneServices(t *testing.T) {
|
|
| 40 |
+ ctx := context.Background() |
|
| 41 |
+ namespace := convert.NewNamespace("foo")
|
|
| 42 |
+ services := map[string]struct{}{
|
|
| 43 |
+ "new": {},
|
|
| 44 |
+ "keep": {},
|
|
| 45 |
+ } |
|
| 46 |
+ client := &fakeClient{serviceList: []string{"foo_keep", "foo_remove"}}
|
|
| 47 |
+ dockerCli := test.NewFakeCli(client, &bytes.Buffer{})
|
|
| 48 |
+ dockerCli.SetErr(&bytes.Buffer{})
|
|
| 49 |
+ |
|
| 50 |
+ pruneServices(ctx, dockerCli, namespace, services) |
|
| 51 |
+ |
|
| 52 |
+ assert.DeepEqual(t, client.removedIDs, []string{"foo_remove"})
|
|
| 53 |
+} |
| ... | ... |
@@ -68,7 +68,7 @@ func runRemove(dockerCli *command.DockerCli, opts removeOptions) error {
|
| 68 | 68 |
|
| 69 | 69 |
func removeServices( |
| 70 | 70 |
ctx context.Context, |
| 71 |
- dockerCli *command.DockerCli, |
|
| 71 |
+ dockerCli command.Cli, |
|
| 72 | 72 |
services []swarm.Service, |
| 73 | 73 |
) bool {
|
| 74 | 74 |
var err error |
| ... | ... |
@@ -83,7 +83,7 @@ func removeServices( |
| 83 | 83 |
|
| 84 | 84 |
func removeNetworks( |
| 85 | 85 |
ctx context.Context, |
| 86 |
- dockerCli *command.DockerCli, |
|
| 86 |
+ dockerCli command.Cli, |
|
| 87 | 87 |
networks []types.NetworkResource, |
| 88 | 88 |
) bool {
|
| 89 | 89 |
var err error |
| ... | ... |
@@ -98,7 +98,7 @@ func removeNetworks( |
| 98 | 98 |
|
| 99 | 99 |
func removeSecrets( |
| 100 | 100 |
ctx context.Context, |
| 101 |
- dockerCli *command.DockerCli, |
|
| 101 |
+ dockerCli command.Cli, |
|
| 102 | 102 |
secrets []swarm.Secret, |
| 103 | 103 |
) bool {
|
| 104 | 104 |
var err error |
| ... | ... |
@@ -2,6 +2,7 @@ package convert |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"io/ioutil" |
| 5 |
+ "strings" |
|
| 5 | 6 |
|
| 6 | 7 |
"github.com/docker/docker/api/types" |
| 7 | 8 |
networktypes "github.com/docker/docker/api/types/network" |
| ... | ... |
@@ -24,6 +25,11 @@ func (n Namespace) Scope(name string) string {
|
| 24 | 24 |
return n.name + "_" + name |
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 |
+// Descope returns the name without the namespace prefix |
|
| 28 |
+func (n Namespace) Descope(name string) string {
|
|
| 29 |
+ return strings.TrimPrefix(name, n.name+"_") |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 27 | 32 |
// Name returns the name of the namespace |
| 28 | 33 |
func (n Namespace) Name() string {
|
| 29 | 34 |
return n.name |
| ... | ... |
@@ -35,7 +35,7 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
|
| 35 | 35 |
c.in = in |
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 |
-// SetErr sets the standard error stream th cli should write on |
|
| 38 |
+// SetErr sets the stderr stream for the cli to the specified io.Writer |
|
| 39 | 39 |
func (c *FakeCli) SetErr(err io.Writer) {
|
| 40 | 40 |
c.err = err |
| 41 | 41 |
} |
| ... | ... |
@@ -30,6 +30,7 @@ Options: |
| 30 | 30 |
--bundle-file string Path to a Distributed Application Bundle file |
| 31 | 31 |
--compose-file string Path to a Compose file |
| 32 | 32 |
--help Print usage |
| 33 |
+ --prune Prune services that are no longer referenced |
|
| 33 | 34 |
--with-registry-auth Send registry authentication details to Swarm agents |
| 34 | 35 |
``` |
| 35 | 36 |
|
| ... | ... |
@@ -27,6 +27,7 @@ Options: |
| 27 | 27 |
--bundle-file string Path to a Distributed Application Bundle file |
| 28 | 28 |
-c, --compose-file string Path to a Compose file |
| 29 | 29 |
--help Print usage |
| 30 |
+ --prune Prune services that are no longer referenced |
|
| 30 | 31 |
--with-registry-auth Send registry authentication details to Swarm agents |
| 31 | 32 |
``` |
| 32 | 33 |
|