diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 5c4996d666..72719f94fc 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -48,3 +48,13 @@ func getStackNetworks( ctx, types.NetworkListOptions{Filters: getStackFilter(namespace)}) } + +func getStackSecrets( + ctx context.Context, + apiclient client.APIClient, + namespace string, +) ([]swarm.Secret, error) { + return apiclient.SecretList( + ctx, + types.SecretListOptions{Filters: getStackFilter(namespace)}) +} diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index 734ff92a53..966c1aa6bf 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -3,11 +3,12 @@ package stack import ( "fmt" - "golang.org/x/net/context" - + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type removeOptions struct { @@ -33,41 +34,79 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { func runRemove(dockerCli *command.DockerCli, opts removeOptions) error { namespace := opts.namespace client := dockerCli.Client() - stderr := dockerCli.Err() ctx := context.Background() - hasError := false services, err := getServices(ctx, client, namespace) if err != nil { return err } - for _, service := range services { - fmt.Fprintf(stderr, "Removing service %s\n", service.Spec.Name) - if err := client.ServiceRemove(ctx, service.ID); err != nil { - hasError = true - fmt.Fprintf(stderr, "Failed to remove service %s: %s", service.ID, err) - } - } networks, err := getStackNetworks(ctx, client, namespace) if err != nil { return err } - for _, network := range networks { - fmt.Fprintf(stderr, "Removing network %s\n", network.Name) - if err := client.NetworkRemove(ctx, network.ID); err != nil { - hasError = true - fmt.Fprintf(stderr, "Failed to remove network %s: %s", network.ID, err) - } + + secrets, err := getStackSecrets(ctx, client, namespace) + if err != nil { + return err } - if len(services) == 0 && len(networks) == 0 { + if len(services)+len(networks)+len(secrets) == 0 { fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace) return nil } + hasError := removeServices(ctx, dockerCli, services) + hasError = removeSecrets(ctx, dockerCli, secrets) || hasError + hasError = removeNetworks(ctx, dockerCli, networks) || hasError + if hasError { return fmt.Errorf("Failed to remove some resources") } return nil } + +func removeServices( + ctx context.Context, + dockerCli *command.DockerCli, + services []swarm.Service, +) bool { + var err error + for _, service := range services { + fmt.Fprintf(dockerCli.Err(), "Removing service %s\n", service.Spec.Name) + if err = dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil { + fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err) + } + } + return err != nil +} + +func removeNetworks( + ctx context.Context, + dockerCli *command.DockerCli, + networks []types.NetworkResource, +) bool { + var err error + for _, network := range networks { + fmt.Fprintf(dockerCli.Err(), "Removing network %s\n", network.Name) + if err = dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil { + fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err) + } + } + return err != nil +} + +func removeSecrets( + ctx context.Context, + dockerCli *command.DockerCli, + secrets []swarm.Secret, +) bool { + var err error + for _, secret := range secrets { + fmt.Fprintf(dockerCli.Err(), "Removing secret %s\n", secret.Spec.Name) + if err = dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil { + fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err) + } + } + return err != nil +} diff --git a/integration-cli/docker_cli_stack_test.go b/integration-cli/docker_cli_stack_test.go index e02fb15917..0785686231 100644 --- a/integration-cli/docker_cli_stack_test.go +++ b/integration-cli/docker_cli_stack_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "sort" + "strings" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/integration-cli/checker" @@ -12,7 +13,7 @@ import ( "github.com/go-check/check" ) -func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { +func (s *DockerSwarmSuite) TestStackRemoveUnknown(c *check.C) { d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "remove", "UNKNOWN_STACK"}) @@ -22,7 +23,7 @@ func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { c.Assert(out, check.Equals, "Nothing found in stack: UNKNOWN_STACK\n") } -func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { +func (s *DockerSwarmSuite) TestStackPSUnknown(c *check.C) { d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "ps", "UNKNOWN_STACK"}) @@ -32,7 +33,7 @@ func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { c.Assert(out, check.Equals, "Nothing found in stack: UNKNOWN_STACK\n") } -func (s *DockerSwarmSuite) TestStackServices(c *check.C) { +func (s *DockerSwarmSuite) TestStackServicesUnknown(c *check.C) { d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "services", "UNKNOWN_STACK"}) @@ -100,6 +101,29 @@ func (s *DockerSwarmSuite) TestStackDeployWithSecretsTwice(c *check.C) { c.Assert(err, checker.IsNil, check.Commentf(out)) } +func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { + d := s.AddDaemon(c, true, true) + + stackName := "testdeploy" + stackArgs := []string{ + "stack", "deploy", + "--compose-file", "fixtures/deploy/remove.yaml", + stackName, + } + out, err := d.Cmd(stackArgs...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + out, err = d.Cmd("stack", "ps", stackName) + c.Assert(err, checker.IsNil) + c.Assert(strings.Split(strings.TrimSpace(out), "\n"), checker.HasLen, 2) + + out, err = d.Cmd("stack", "rm", stackName) + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Removing service testdeploy_web") + c.Assert(out, checker.Contains, "Removing network testdeploy_default") + c.Assert(out, checker.Contains, "Removing secret testdeploy_special") +} + type sortSecrets []swarm.SecretReference func (s sortSecrets) Len() int { return len(s) } diff --git a/integration-cli/fixtures/deploy/remove.yaml b/integration-cli/fixtures/deploy/remove.yaml new file mode 100644 index 0000000000..4ec8cacc9b --- /dev/null +++ b/integration-cli/fixtures/deploy/remove.yaml @@ -0,0 +1,11 @@ + +version: "3.1" +services: + web: + image: busybox@sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0 + command: top + secrets: + - special +secrets: + special: + file: fixtures/secrets/default