2016-09-08 13:11:39 -04:00
|
|
|
package stack
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-08-14 14:24:11 -04:00
|
|
|
"sort"
|
2017-03-26 02:23:24 -04:00
|
|
|
"strings"
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2017-01-18 14:40:29 -05:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
2017-06-12 16:14:05 -04:00
|
|
|
"github.com/docker/docker/api/types/versions"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
2017-01-18 14:40:29 -05:00
|
|
|
"golang.org/x/net/context"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type removeOptions struct {
|
2017-03-26 02:23:24 -04:00
|
|
|
namespaces []string
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
2016-09-08 13:11:39 -04:00
|
|
|
var opts removeOptions
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
2017-03-26 02:23:24 -04:00
|
|
|
Use: "rm STACK [STACK...]",
|
2016-09-08 13:11:39 -04:00
|
|
|
Aliases: []string{"remove", "down"},
|
2017-03-26 02:23:24 -04:00
|
|
|
Short: "Remove one or more stacks",
|
|
|
|
Args: cli.RequiresMinArgs(1),
|
2016-09-08 13:11:39 -04:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2017-03-26 02:23:24 -04:00
|
|
|
opts.namespaces = args
|
2016-09-08 13:11:39 -04:00
|
|
|
return runRemove(dockerCli, opts)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
func runRemove(dockerCli command.Cli, opts removeOptions) error {
|
|
|
|
namespaces := opts.namespaces
|
2016-09-08 13:11:39 -04:00
|
|
|
client := dockerCli.Client()
|
|
|
|
ctx := context.Background()
|
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
var errs []string
|
|
|
|
for _, namespace := range namespaces {
|
|
|
|
services, err := getServices(ctx, client, namespace)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
networks, err := getStackNetworks(ctx, client, namespace)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-01-18 14:40:29 -05:00
|
|
|
|
Don't attempt to remove unsupported resources on older daemon
When running `docker stack rm <some stack>` against an older daemon,
a warning was printed for "configs" being ignored;
WARNING: ignoring "configs" (requires API version 1.30, but the Docker daemon API version is 1.26)
Given that an old daemon cannot _have_ configs, there should not be
a need to warn, or _attempt_ to remove these resources.
This patch removes the warning, and skips fetching (and removing)
configs.
A check if _secrets_ are supported by the daemon is also added,
given that this would result in an error when attempted against
an older (pre 1.13) daemon.
There is one situation where this could lead to secrets or
configs being left behind; if the client is connecting to a
daemon that _does_ support secrets, configs, but the API version
is overridden using `DOCKER_API_VERSION`, no warning is printed,
and secrets and configs are not attempted to be removed.
Given that `DOCKER_API_VERSION` is regarded a feature for
debugging / "power users", it should be ok to ignore this.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-30 20:00:16 -04:00
|
|
|
var secrets []swarm.Secret
|
|
|
|
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
|
|
|
|
secrets, err = getStackSecrets(ctx, client, namespace)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-26 02:23:24 -04:00
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-06-12 16:14:05 -04:00
|
|
|
var configs []swarm.Config
|
Don't attempt to remove unsupported resources on older daemon
When running `docker stack rm <some stack>` against an older daemon,
a warning was printed for "configs" being ignored;
WARNING: ignoring "configs" (requires API version 1.30, but the Docker daemon API version is 1.26)
Given that an old daemon cannot _have_ configs, there should not be
a need to warn, or _attempt_ to remove these resources.
This patch removes the warning, and skips fetching (and removing)
configs.
A check if _secrets_ are supported by the daemon is also added,
given that this would result in an error when attempted against
an older (pre 1.13) daemon.
There is one situation where this could lead to secrets or
configs being left behind; if the client is connecting to a
daemon that _does_ support secrets, configs, but the API version
is overridden using `DOCKER_API_VERSION`, no warning is printed,
and secrets and configs are not attempted to be removed.
Given that `DOCKER_API_VERSION` is regarded a feature for
debugging / "power users", it should be ok to ignore this.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-30 20:00:16 -04:00
|
|
|
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
|
2017-06-12 16:14:05 -04:00
|
|
|
configs, err = getStackConfigs(ctx, client, namespace)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-05-26 20:30:33 -04:00
|
|
|
|
|
|
|
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
|
2017-07-05 13:32:54 -04:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
|
2017-03-26 02:23:24 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
hasError := removeServices(ctx, dockerCli, services)
|
|
|
|
hasError = removeSecrets(ctx, dockerCli, secrets) || hasError
|
2017-05-26 20:30:33 -04:00
|
|
|
hasError = removeConfigs(ctx, dockerCli, configs) || hasError
|
2017-03-26 02:23:24 -04:00
|
|
|
hasError = removeNetworks(ctx, dockerCli, networks) || hasError
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
if hasError {
|
|
|
|
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
|
|
|
|
}
|
|
|
|
}
|
2017-01-18 14:40:29 -05:00
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
if len(errs) > 0 {
|
|
|
|
return errors.Errorf(strings.Join(errs, "\n"))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2017-01-18 14:40:29 -05:00
|
|
|
|
2017-08-14 14:24:11 -04:00
|
|
|
func sortServiceByName(services []swarm.Service) func(i, j int) bool {
|
|
|
|
return func(i, j int) bool {
|
|
|
|
return services[i].Spec.Name < services[j].Spec.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-18 14:40:29 -05:00
|
|
|
func removeServices(
|
|
|
|
ctx context.Context,
|
2017-02-22 15:43:13 -05:00
|
|
|
dockerCli command.Cli,
|
2017-01-18 14:40:29 -05:00
|
|
|
services []swarm.Service,
|
|
|
|
) bool {
|
2017-07-05 00:04:37 -04:00
|
|
|
var hasError bool
|
2017-08-14 14:24:11 -04:00
|
|
|
sort.Slice(services, sortServiceByName(services))
|
2017-01-18 14:40:29 -05:00
|
|
|
for _, service := range services {
|
2017-08-31 22:42:34 -04:00
|
|
|
fmt.Fprintf(dockerCli.Out(), "Removing service %s\n", service.Spec.Name)
|
2017-07-05 00:04:37 -04:00
|
|
|
if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
|
|
|
|
hasError = true
|
2017-01-18 14:40:29 -05:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
|
|
|
|
}
|
|
|
|
}
|
2017-07-05 00:04:37 -04:00
|
|
|
return hasError
|
2017-01-18 14:40:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func removeNetworks(
|
|
|
|
ctx context.Context,
|
2017-02-22 15:43:13 -05:00
|
|
|
dockerCli command.Cli,
|
2017-01-18 14:40:29 -05:00
|
|
|
networks []types.NetworkResource,
|
|
|
|
) bool {
|
2017-07-05 00:04:37 -04:00
|
|
|
var hasError bool
|
2017-01-18 14:40:29 -05:00
|
|
|
for _, network := range networks {
|
2017-08-31 22:42:34 -04:00
|
|
|
fmt.Fprintf(dockerCli.Out(), "Removing network %s\n", network.Name)
|
2017-07-05 00:04:37 -04:00
|
|
|
if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
|
|
|
|
hasError = true
|
2017-01-18 14:40:29 -05:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
|
|
|
|
}
|
|
|
|
}
|
2017-07-05 00:04:37 -04:00
|
|
|
return hasError
|
2017-01-18 14:40:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func removeSecrets(
|
|
|
|
ctx context.Context,
|
2017-02-22 15:43:13 -05:00
|
|
|
dockerCli command.Cli,
|
2017-01-18 14:40:29 -05:00
|
|
|
secrets []swarm.Secret,
|
|
|
|
) bool {
|
2017-07-05 00:04:37 -04:00
|
|
|
var hasError bool
|
2017-01-18 14:40:29 -05:00
|
|
|
for _, secret := range secrets {
|
2017-08-31 22:42:34 -04:00
|
|
|
fmt.Fprintf(dockerCli.Out(), "Removing secret %s\n", secret.Spec.Name)
|
2017-07-05 00:04:37 -04:00
|
|
|
if err := dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
|
|
|
|
hasError = true
|
2017-01-18 14:40:29 -05:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
|
|
|
|
}
|
|
|
|
}
|
2017-07-05 00:04:37 -04:00
|
|
|
return hasError
|
2017-01-18 14:40:29 -05:00
|
|
|
}
|
2017-05-26 20:30:33 -04:00
|
|
|
|
|
|
|
func removeConfigs(
|
|
|
|
ctx context.Context,
|
|
|
|
dockerCli command.Cli,
|
|
|
|
configs []swarm.Config,
|
|
|
|
) bool {
|
2017-07-05 00:04:37 -04:00
|
|
|
var hasError bool
|
2017-05-26 20:30:33 -04:00
|
|
|
for _, config := range configs {
|
2017-08-31 22:42:34 -04:00
|
|
|
fmt.Fprintf(dockerCli.Out(), "Removing config %s\n", config.Spec.Name)
|
2017-07-05 00:04:37 -04:00
|
|
|
if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
|
|
|
|
hasError = true
|
2017-05-26 20:30:33 -04:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)
|
|
|
|
}
|
|
|
|
}
|
2017-07-05 00:04:37 -04:00
|
|
|
return hasError
|
2017-05-26 20:30:33 -04:00
|
|
|
}
|