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>
154 lines
3.8 KiB
Go
154 lines
3.8 KiB
Go
package stack
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/api/types/versions"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type removeOptions struct {
|
|
namespaces []string
|
|
}
|
|
|
|
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
|
var opts removeOptions
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "rm STACK [STACK...]",
|
|
Aliases: []string{"remove", "down"},
|
|
Short: "Remove one or more stacks",
|
|
Args: cli.RequiresMinArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
opts.namespaces = args
|
|
return runRemove(dockerCli, opts)
|
|
},
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func runRemove(dockerCli command.Cli, opts removeOptions) error {
|
|
namespaces := opts.namespaces
|
|
client := dockerCli.Client()
|
|
ctx := context.Background()
|
|
|
|
var errs []string
|
|
for _, namespace := range namespaces {
|
|
services, err := getServices(ctx, client, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
networks, err := getStackNetworks(ctx, client, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var secrets []swarm.Secret
|
|
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
|
|
secrets, err = getStackSecrets(ctx, client, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var configs []swarm.Config
|
|
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
|
|
configs, err = getStackConfigs(ctx, client, namespace)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
|
|
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
|
|
continue
|
|
}
|
|
|
|
hasError := removeServices(ctx, dockerCli, services)
|
|
hasError = removeSecrets(ctx, dockerCli, secrets) || hasError
|
|
hasError = removeConfigs(ctx, dockerCli, configs) || hasError
|
|
hasError = removeNetworks(ctx, dockerCli, networks) || hasError
|
|
|
|
if hasError {
|
|
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Errorf(strings.Join(errs, "\n"))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func removeServices(
|
|
ctx context.Context,
|
|
dockerCli command.Cli,
|
|
services []swarm.Service,
|
|
) bool {
|
|
var hasError bool
|
|
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 {
|
|
hasError = true
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
|
|
}
|
|
}
|
|
return hasError
|
|
}
|
|
|
|
func removeNetworks(
|
|
ctx context.Context,
|
|
dockerCli command.Cli,
|
|
networks []types.NetworkResource,
|
|
) bool {
|
|
var hasError bool
|
|
for _, network := range networks {
|
|
fmt.Fprintf(dockerCli.Err(), "Removing network %s\n", network.Name)
|
|
if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
|
|
hasError = true
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
|
|
}
|
|
}
|
|
return hasError
|
|
}
|
|
|
|
func removeSecrets(
|
|
ctx context.Context,
|
|
dockerCli command.Cli,
|
|
secrets []swarm.Secret,
|
|
) bool {
|
|
var hasError bool
|
|
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 {
|
|
hasError = true
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
|
|
}
|
|
}
|
|
return hasError
|
|
}
|
|
|
|
func removeConfigs(
|
|
ctx context.Context,
|
|
dockerCli command.Cli,
|
|
configs []swarm.Config,
|
|
) bool {
|
|
var hasError bool
|
|
for _, config := range configs {
|
|
fmt.Fprintf(dockerCli.Err(), "Removing config %s\n", config.Spec.Name)
|
|
if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
|
|
hasError = true
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)
|
|
}
|
|
}
|
|
return hasError
|
|
}
|