2017-11-20 09:30:52 -05:00
|
|
|
package swarm
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2016-09-08 13:11:39 -04:00
|
|
|
"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/command"
|
2017-12-04 06:30:39 -05:00
|
|
|
"github.com/docker/cli/cli/command/stack/options"
|
2024-06-05 10:28:24 -04:00
|
|
|
"github.com/docker/docker/api/types/network"
|
2017-01-18 14:40:29 -05:00
|
|
|
"github.com/docker/docker/api/types/swarm"
|
2017-06-12 16:14:05 -04:00
|
|
|
"github.com/docker/docker/api/types/versions"
|
2024-06-09 09:53:08 -04:00
|
|
|
"github.com/docker/docker/client"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
2017-12-04 06:30:39 -05:00
|
|
|
// RunRemove is the swarm implementation of docker stack remove
|
2023-09-09 18:27:44 -04:00
|
|
|
func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove) error {
|
2024-06-09 09:53:08 -04:00
|
|
|
apiClient := dockerCli.Client()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-03-26 02:23:24 -04:00
|
|
|
var errs []string
|
2018-05-28 06:21:41 -04:00
|
|
|
for _, namespace := range opts.Namespaces {
|
2024-06-09 09:53:08 -04:00
|
|
|
services, err := getStackServices(ctx, apiClient, namespace)
|
2017-03-26 02:23:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2024-06-09 09:53:08 -04:00
|
|
|
networks, err := getStackNetworks(ctx, apiClient, namespace)
|
2017-03-26 02:23:24 -04:00
|
|
|
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
|
2024-06-09 09:53:08 -04:00
|
|
|
if versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.25") {
|
|
|
|
secrets, err = getStackSecrets(ctx, apiClient, namespace)
|
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 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
|
2024-06-09 09:53:08 -04:00
|
|
|
if versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") {
|
|
|
|
configs, err = getStackConfigs(ctx, apiClient, namespace)
|
2017-06-12 16:14:05 -04:00
|
|
|
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 {
|
linting: fmt.Sprintf can be replaced with string concatenation (perfsprint)
cli/registry/client/endpoint.go:128:34: fmt.Sprintf can be replaced with string concatenation (perfsprint)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
^
cli/command/telemetry_docker.go:88:14: fmt.Sprintf can be replaced with string concatenation (perfsprint)
endpoint = fmt.Sprintf("unix://%s", path.Join(u.Host, u.Path))
^
cli/command/cli_test.go:195:47: fmt.Sprintf can be replaced with string concatenation (perfsprint)
opts := &flags.ClientOptions{Hosts: []string{fmt.Sprintf("unix://%s", socket)}}
^
cli/command/registry_test.go:59:24: fmt.Sprintf can be replaced with string concatenation (perfsprint)
inputServerAddress: fmt.Sprintf("https://%s", testAuthConfigs[1].ServerAddress),
^
cli/command/container/opts_test.go:338:35: fmt.Sprintf can be replaced with string concatenation (perfsprint)
if config, _, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
^
cli/command/context/options.go:79:24: fmt.Sprintf can be replaced with string concatenation (perfsprint)
errs = append(errs, fmt.Sprintf("%s: unrecognized config key", k))
^
cli/command/image/build.go:461:68: fmt.Sprintf can be replaced with string concatenation (perfsprint)
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef)))
^
cli/command/image/remove_test.go:21:9: fmt.Sprintf can be replaced with string concatenation (perfsprint)
return fmt.Sprintf("Error: No such image: %s", n.imageID)
^
cli/command/image/build/context.go:229:102: fmt.Sprintf can be replaced with string concatenation (perfsprint)
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
^
cli/command/service/logs.go:215:16: fmt.Sprintf can be replaced with string concatenation (perfsprint)
taskName += fmt.Sprintf(".%s", task.ID)
^
cli/command/service/logs.go:217:16: fmt.Sprintf can be replaced with string concatenation (perfsprint)
taskName += fmt.Sprintf(".%s", stringid.TruncateID(task.ID))
^
cli/command/service/progress/progress_test.go:877:18: fmt.Sprintf can be replaced with string concatenation (perfsprint)
ID: fmt.Sprintf("task%s", nodeID),
^
cli/command/stack/swarm/remove.go:61:24: fmt.Sprintf can be replaced with string concatenation (perfsprint)
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
^
cli/command/swarm/ipnet_slice_test.go:32:9: fmt.Sprintf can be replaced with string concatenation (perfsprint)
arg := fmt.Sprintf("--cidrs=%s", strings.Join(vals, ","))
^
cli/command/swarm/ipnet_slice_test.go:137:30: fmt.Sprintf can be replaced with string concatenation (perfsprint)
if err := f.Parse([]string{fmt.Sprintf("--cidrs=%s", strings.Join(test.FlagArg, ","))}); err != nil {
^
cli/compose/schema/schema.go:105:11: fmt.Sprintf can be replaced with string concatenation (perfsprint)
return fmt.Sprintf("must be a %s", humanReadableType(expectedType))
^
cli/manifest/store/store.go:165:9: fmt.Sprintf can be replaced with string concatenation (perfsprint)
return fmt.Sprintf("No such manifest: %s", n.object)
^
e2e/image/push_test.go:340:4: fmt.Sprintf can be replaced with string concatenation (perfsprint)
fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
^
e2e/image/push_test.go:341:4: fmt.Sprintf can be replaced with string concatenation (perfsprint)
fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
^
e2e/image/push_test.go:342:4: fmt.Sprintf can be replaced with string concatenation (perfsprint)
fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
^
e2e/image/push_test.go:343:4: fmt.Sprintf can be replaced with string concatenation (perfsprint)
fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
^
e2e/plugin/trust_test.go:23:16: fmt.Sprintf can be replaced with string concatenation (perfsprint)
pluginName := fmt.Sprintf("%s/plugin-content-trust", registryPrefix)
^
e2e/plugin/trust_test.go:53:8: fmt.Sprintf can be replaced with string concatenation (perfsprint)
Out: fmt.Sprintf("Installed plugin %s", pluginName),
^
e2e/trust/revoke_test.go:62:57: fmt.Sprintf can be replaced with string concatenation (perfsprint)
icmd.RunCommand("docker", "tag", fixtures.AlpineImage, fmt.Sprintf("%s:v1", revokeRepo)).Assert(t, icmd.Success)
^
e2e/trust/revoke_test.go:64:49: fmt.Sprintf can be replaced with string concatenation (perfsprint)
icmd.Command("docker", "-D", "trust", "sign", fmt.Sprintf("%s:v1", revokeRepo)),
^
e2e/trust/revoke_test.go:68:58: fmt.Sprintf can be replaced with string concatenation (perfsprint)
icmd.RunCommand("docker", "tag", fixtures.BusyboxImage, fmt.Sprintf("%s:v2", revokeRepo)).Assert(t, icmd.Success)
^
e2e/trust/revoke_test.go:70:49: fmt.Sprintf can be replaced with string concatenation (perfsprint)
icmd.Command("docker", "-D", "trust", "sign", fmt.Sprintf("%s:v2", revokeRepo)),
^
e2e/trust/sign_test.go:36:47: fmt.Sprintf can be replaced with string concatenation (perfsprint)
assert.Check(t, is.Contains(result.Stdout(), fmt.Sprintf("v1: digest: sha256:%s", fixtures.AlpineSha)))
^
e2e/trust/sign_test.go:53:47: fmt.Sprintf can be replaced with string concatenation (perfsprint)
assert.Check(t, is.Contains(result.Stdout(), fmt.Sprintf("v1: digest: sha256:%s", fixtures.BusyboxSha)))
^
e2e/trust/sign_test.go:65:47: fmt.Sprintf can be replaced with string concatenation (perfsprint)
assert.Check(t, is.Contains(result.Stdout(), fmt.Sprintf("v1: digest: sha256:%s", fixtures.AlpineSha)))
^
opts/file.go:21:9: fmt.Sprintf can be replaced with string concatenation (perfsprint)
return fmt.Sprintf("poorly formatted environment: %s", e.msg)
^
opts/hosts_test.go:26:31: fmt.Sprintf can be replaced with string concatenation (perfsprint)
"tcp://host:": fmt.Sprintf("tcp://host:%s", defaultHTTPPort),
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-06-10 15:07:37 -04:00
|
|
|
errs = append(errs, "Failed to remove some resources from stack: "+namespace)
|
2023-05-05 08:36:36 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !opts.Detach {
|
2024-06-09 09:53:08 -04:00
|
|
|
err = waitOnTasks(ctx, apiClient, namespace)
|
2023-05-05 08:36:36 -04:00
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, fmt.Sprintf("Failed to wait on tasks of stack: %s: %s", namespace, err))
|
|
|
|
}
|
2017-03-26 02:23:24 -04:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-09 09:53:08 -04:00
|
|
|
func removeServices(ctx context.Context, dockerCli command.Cli, 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
|
|
|
}
|
|
|
|
|
2024-06-09 09:53:08 -04:00
|
|
|
func removeNetworks(ctx context.Context, dockerCli command.Cli, networks []network.Summary) bool {
|
2017-07-05 00:04:37 -04:00
|
|
|
var hasError bool
|
2024-06-05 10:28:24 -04:00
|
|
|
for _, nw := range networks {
|
|
|
|
fmt.Fprintf(dockerCli.Out(), "Removing network %s\n", nw.Name)
|
|
|
|
if err := dockerCli.Client().NetworkRemove(ctx, nw.ID); err != nil {
|
2017-07-05 00:04:37 -04:00
|
|
|
hasError = true
|
2024-06-05 10:28:24 -04:00
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", nw.ID, err)
|
2017-01-18 14:40:29 -05:00
|
|
|
}
|
|
|
|
}
|
2017-07-05 00:04:37 -04:00
|
|
|
return hasError
|
2017-01-18 14:40:29 -05:00
|
|
|
}
|
|
|
|
|
2024-06-09 09:53:08 -04:00
|
|
|
func removeSecrets(ctx context.Context, dockerCli command.Cli, 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
|
|
|
|
2024-06-09 09:53:08 -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
|
|
|
}
|
2023-05-05 08:36:36 -04:00
|
|
|
|
|
|
|
var numberedStates = map[swarm.TaskState]int64{
|
|
|
|
swarm.TaskStateNew: 1,
|
|
|
|
swarm.TaskStateAllocated: 2,
|
|
|
|
swarm.TaskStatePending: 3,
|
|
|
|
swarm.TaskStateAssigned: 4,
|
|
|
|
swarm.TaskStateAccepted: 5,
|
|
|
|
swarm.TaskStatePreparing: 6,
|
|
|
|
swarm.TaskStateReady: 7,
|
|
|
|
swarm.TaskStateStarting: 8,
|
|
|
|
swarm.TaskStateRunning: 9,
|
|
|
|
swarm.TaskStateComplete: 10,
|
|
|
|
swarm.TaskStateShutdown: 11,
|
|
|
|
swarm.TaskStateFailed: 12,
|
|
|
|
swarm.TaskStateRejected: 13,
|
|
|
|
}
|
|
|
|
|
|
|
|
func terminalState(state swarm.TaskState) bool {
|
|
|
|
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
|
|
|
|
}
|
|
|
|
|
2024-06-09 09:53:08 -04:00
|
|
|
func waitOnTasks(ctx context.Context, apiClient client.APIClient, namespace string) error {
|
2023-05-05 08:36:36 -04:00
|
|
|
terminalStatesReached := 0
|
|
|
|
for {
|
2024-06-09 09:53:08 -04:00
|
|
|
tasks, err := getStackTasks(ctx, apiClient, namespace)
|
2023-05-05 08:36:36 -04:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get tasks: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, task := range tasks {
|
|
|
|
if terminalState(task.Status.State) {
|
|
|
|
terminalStatesReached++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if terminalStatesReached == len(tasks) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|