Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/command/stack/options/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type PS struct {
// Remove holds docker stack remove options
type Remove struct {
Namespaces []string
Detach bool
}

// Services holds docker stack services options
Expand Down
3 changes: 3 additions & 0 deletions cli/command/stack/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
return completeNames(dockerCli)(cmd, args, toComplete)
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.Detach, "detach", "d", true, "Do not wait for stack removal")
return cmd
}
4 changes: 4 additions & 0 deletions cli/command/stack/swarm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ func getStackSecrets(ctx context.Context, apiclient client.APIClient, namespace
func getStackConfigs(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Config, error) {
return apiclient.ConfigList(ctx, types.ConfigListOptions{Filters: getStackFilter(namespace)})
}

func getStackTasks(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Task, error) {
return apiclient.TaskList(ctx, types.TaskListOptions{Filters: getStackFilter(namespace)})
}
51 changes: 51 additions & 0 deletions cli/command/stack/swarm/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
apiclient "github.com/docker/docker/client"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -58,6 +59,14 @@ func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove)

if hasError {
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
continue
}

if !opts.Detach {
err = waitOnTasks(ctx, client, namespace)
if err != nil {
errs = append(errs, fmt.Sprintf("Failed to wait on tasks of stack: %s: %s", namespace, err))
}
}
}

Expand Down Expand Up @@ -137,3 +146,45 @@ func removeConfigs(
}
return hasError
}

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,
}
Comment on lines +150 to +164
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should rely on importing SwarmKit here instead of incorporating magic enums.

Also it's unclear if this logic even belongs on the client; this looks dangerously close to leaking concerns across layers.

Copy link
Contributor Author

@gmargaritis gmargaritis May 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @neersighted, thanks a lot for getting back to me!

This map was inspired by the progress code for Service:

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,
// The following states are not actually shown in progress
// output, but are used internally for ordering.
swarm.TaskStateComplete: 10,
swarm.TaskStateShutdown: 11,
swarm.TaskStateFailed: 12,
swarm.TaskStateRejected: 13,
}
longestState int
)

func terminalState(state swarm.TaskState) bool {
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
}

An alternative would be to create a set with all the terminal states and check against that instead.

By design, the server instantly acknowledges any changes (e.g. removing a stack) and then the action is asynchronous. Thus, I believe that this logic belongs to the client, as the client is the one that waits for the actions to complete.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason Swarm states are numbered like they are is so that intermediate states can be inserted at any time without disrupting the ordering of states committed to the object store already. If the code handling states isn't persistent, then there's no such concern. If we ever added an intermediate state, we can just renumber all of these harmlessly.


func terminalState(state swarm.TaskState) bool {
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
}

func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace string) error {
terminalStatesReached := 0
for {
tasks, err := getStackTasks(ctx, client, namespace)
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
}
6 changes: 6 additions & 0 deletions docs/reference/commandline/stack_rm.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ Remove one or more stacks

`docker stack rm`, `docker stack remove`, `docker stack down`

### Options

| Name | Type | Default | Description |
|:-----------------|:-------|:--------|:------------------------------|
| `-d`, `--detach` | `bool` | `true` | Do not wait for stack removal |


<!---MARKER_GEN_END-->

Expand Down