Skip to content

Commit 4a43b8e

Browse files
authored
Merge pull request #4258 from gmargaritis/373-support-detach-in-docker-stack-deploy
Add support for --detach flag in stack deploy
2 parents 35e6a41 + b086d72 commit 4a43b8e

File tree

13 files changed

+76
-27
lines changed

13 files changed

+76
-27
lines changed

cli/command/service/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
137137
return nil
138138
}
139139

140-
return waitOnService(ctx, dockerCli, response.ID, opts.quiet)
140+
return WaitOnService(ctx, dockerCli, response.ID, opts.quiet)
141141
}
142142

143143
// setConfigs does double duty: it both sets the ConfigReferences of the

cli/command/service/helpers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"github.com/docker/docker/pkg/jsonmessage"
1010
)
1111

12-
// waitOnService waits for the service to converge. It outputs a progress bar,
12+
// WaitOnService waits for the service to converge. It outputs a progress bar,
1313
// if appropriate based on the CLI flags.
14-
func waitOnService(ctx context.Context, dockerCli command.Cli, serviceID string, quiet bool) error {
14+
func WaitOnService(ctx context.Context, dockerCli command.Cli, serviceID string, quiet bool) error {
1515
errChan := make(chan error, 1)
1616
pipeReader, pipeWriter := io.Pipe()
1717

cli/command/service/progress/progress.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
143143
if converged && time.Since(convergedAt) >= monitor {
144144
progressOut.WriteProgress(progress.Progress{
145145
ID: "verify",
146-
Action: "Service converged",
146+
Action: fmt.Sprintf("Service %s converged", serviceID),
147147
})
148148
if message != nil {
149149
progressOut.WriteProgress(*message)

cli/command/service/rollback.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,5 @@ func runRollback(ctx context.Context, dockerCli command.Cli, options *serviceOpt
6262
return nil
6363
}
6464

65-
return waitOnService(ctx, dockerCli, serviceID, options.quiet)
65+
return WaitOnService(ctx, dockerCli, serviceID, options.quiet)
6666
}

cli/command/service/scale.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func runScale(ctx context.Context, dockerCli command.Cli, options *scaleOptions,
8080
if len(serviceIDs) > 0 {
8181
if !options.detach && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.29") {
8282
for _, serviceID := range serviceIDs {
83-
if err := waitOnService(ctx, dockerCli, serviceID, false); err != nil {
83+
if err := WaitOnService(ctx, dockerCli, serviceID, false); err != nil {
8484
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
8585
}
8686
}

cli/command/service/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
249249
return nil
250250
}
251251

252-
return waitOnService(ctx, dockerCli, serviceID, options.quiet)
252+
return WaitOnService(ctx, dockerCli, serviceID, options.quiet)
253253
}
254254

255255
//nolint:gocyclo

cli/command/stack/deploy.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
2626
if err != nil {
2727
return err
2828
}
29-
return swarm.RunDeploy(cmd.Context(), dockerCli, opts, config)
29+
return swarm.RunDeploy(cmd.Context(), dockerCli, cmd.Flags(), &opts, config)
3030
},
3131
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
3232
return completeNames(dockerCli)(cmd, args, toComplete)
@@ -42,5 +42,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
4242
flags.StringVar(&opts.ResolveImage, "resolve-image", swarm.ResolveImageAlways,
4343
`Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`", "`+swarm.ResolveImageChanged+`", "`+swarm.ResolveImageNever+`")`)
4444
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
45+
flags.BoolVarP(&opts.Detach, "detach", "d", true, "Exit immediately instead of waiting for the stack services to converge")
46+
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Suppress progress output")
4547
return cmd
4648
}

cli/command/stack/options/opts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ type Deploy struct {
99
ResolveImage string
1010
SendRegistryAuth bool
1111
Prune bool
12+
Detach bool
13+
Quiet bool
1214
}
1315

1416
// Config holds docker stack config options

cli/command/stack/swarm/deploy.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/docker/docker/api/types/swarm"
1212
"github.com/docker/docker/api/types/versions"
1313
"github.com/pkg/errors"
14+
"github.com/spf13/pflag"
1415
)
1516

1617
// Resolve image constants
@@ -22,8 +23,8 @@ const (
2223
)
2324

2425
// RunDeploy is the swarm implementation of docker stack deploy
25-
func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy, cfg *composetypes.Config) error {
26-
if err := validateResolveImageFlag(&opts); err != nil {
26+
func RunDeploy(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opts *options.Deploy, cfg *composetypes.Config) error {
27+
if err := validateResolveImageFlag(opts); err != nil {
2728
return err
2829
}
2930
// client side image resolution should not be done when the supported
@@ -32,6 +33,11 @@ func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy,
3233
opts.ResolveImage = ResolveImageNever
3334
}
3435

36+
if opts.Detach && !flags.Changed("detach") {
37+
fmt.Fprintln(dockerCli.Err(), "Since --detach=false was not specified, tasks will be created in the background.\n"+
38+
"In a future release, --detach=false will become the default.")
39+
}
40+
3541
return deployCompose(ctx, dockerCli, opts, cfg)
3642
}
3743

cli/command/stack/swarm/deploy_composefile.go

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package swarm
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67

78
"github.com/docker/cli/cli/command"
9+
servicecli "github.com/docker/cli/cli/command/service"
810
"github.com/docker/cli/cli/command/stack/options"
911
"github.com/docker/cli/cli/compose/convert"
1012
composetypes "github.com/docker/cli/cli/compose/types"
@@ -13,10 +15,9 @@ import (
1315
"github.com/docker/docker/api/types/swarm"
1416
apiclient "github.com/docker/docker/client"
1517
"github.com/docker/docker/errdefs"
16-
"github.com/pkg/errors"
1718
)
1819

19-
func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, config *composetypes.Config) error {
20+
func deployCompose(ctx context.Context, dockerCli command.Cli, opts *options.Deploy, config *composetypes.Config) error {
2021
if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
2122
return err
2223
}
@@ -60,7 +61,17 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
6061
if err != nil {
6162
return err
6263
}
63-
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
64+
65+
serviceIDs, err := deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
66+
if err != nil {
67+
return err
68+
}
69+
70+
if opts.Detach {
71+
return nil
72+
}
73+
74+
return waitOnServices(ctx, dockerCli, serviceIDs, opts.Quiet)
6475
}
6576

6677
func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
@@ -87,11 +98,11 @@ func validateExternalNetworks(ctx context.Context, client apiclient.NetworkAPICl
8798
network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
8899
switch {
89100
case errdefs.IsNotFound(err):
90-
return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
101+
return fmt.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
91102
case err != nil:
92103
return err
93104
case network.Scope != "swarm":
94-
return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
105+
return fmt.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
95106
}
96107
}
97108
return nil
@@ -106,13 +117,13 @@ func createSecrets(ctx context.Context, dockerCli command.Cli, secrets []swarm.S
106117
case err == nil:
107118
// secret already exists, then we update that
108119
if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
109-
return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name)
120+
return fmt.Errorf("failed to update secret %s: %w", secretSpec.Name, err)
110121
}
111122
case errdefs.IsNotFound(err):
112123
// secret does not exist, then we create a new one.
113124
fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name)
114125
if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
115-
return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name)
126+
return fmt.Errorf("failed to create secret %s: %w", secretSpec.Name, err)
116127
}
117128
default:
118129
return err
@@ -130,13 +141,13 @@ func createConfigs(ctx context.Context, dockerCli command.Cli, configs []swarm.C
130141
case err == nil:
131142
// config already exists, then we update that
132143
if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
133-
return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
144+
return fmt.Errorf("failed to update config %s: %w", configSpec.Name, err)
134145
}
135146
case errdefs.IsNotFound(err):
136147
// config does not exist, then we create a new one.
137148
fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
138149
if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
139-
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
150+
return fmt.Errorf("failed to create config %s: %w", configSpec.Name, err)
140151
}
141152
default:
142153
return err
@@ -169,26 +180,28 @@ func createNetworks(ctx context.Context, dockerCli command.Cli, namespace conver
169180

170181
fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
171182
if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
172-
return errors.Wrapf(err, "failed to create network %s", name)
183+
return fmt.Errorf("failed to create network %s: %w", name, err)
173184
}
174185
}
175186
return nil
176187
}
177188

178-
func deployServices(ctx context.Context, dockerCli command.Cli, services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, resolveImage string) error {
189+
func deployServices(ctx context.Context, dockerCli command.Cli, services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, resolveImage string) ([]string, error) {
179190
apiClient := dockerCli.Client()
180191
out := dockerCli.Out()
181192

182193
existingServices, err := getStackServices(ctx, apiClient, namespace.Name())
183194
if err != nil {
184-
return err
195+
return nil, err
185196
}
186197

187198
existingServiceMap := make(map[string]swarm.Service)
188199
for _, service := range existingServices {
189200
existingServiceMap[service.Spec.Name] = service
190201
}
191202

203+
var serviceIDs []string
204+
192205
for internalName, serviceSpec := range services {
193206
var (
194207
name = namespace.Scope(internalName)
@@ -200,7 +213,7 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
200213
// Retrieve encoded auth token from the image reference
201214
encodedAuth, err = command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), image)
202215
if err != nil {
203-
return err
216+
return nil, err
204217
}
205218
}
206219

@@ -241,12 +254,14 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
241254

242255
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, serviceSpec, updateOpts)
243256
if err != nil {
244-
return errors.Wrapf(err, "failed to update service %s", name)
257+
return nil, fmt.Errorf("failed to update service %s: %w", name, err)
245258
}
246259

247260
for _, warning := range response.Warnings {
248261
fmt.Fprintln(dockerCli.Err(), warning)
249262
}
263+
264+
serviceIDs = append(serviceIDs, service.ID)
250265
} else {
251266
fmt.Fprintf(out, "Creating service %s\n", name)
252267

@@ -257,10 +272,29 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
257272
createOpts.QueryRegistry = true
258273
}
259274

260-
if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
261-
return errors.Wrapf(err, "failed to create service %s", name)
275+
response, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts)
276+
if err != nil {
277+
return nil, fmt.Errorf("failed to create service %s: %w", name, err)
262278
}
279+
280+
serviceIDs = append(serviceIDs, response.ID)
281+
}
282+
}
283+
284+
return serviceIDs, nil
285+
}
286+
287+
func waitOnServices(ctx context.Context, dockerCli command.Cli, serviceIDs []string, quiet bool) error {
288+
var errs []error
289+
for _, serviceID := range serviceIDs {
290+
if err := servicecli.WaitOnService(ctx, dockerCli, serviceID, quiet); err != nil {
291+
errs = append(errs, fmt.Errorf("%s: %w", serviceID, err))
263292
}
264293
}
294+
295+
if len(errs) > 0 {
296+
return errors.Join(errs...)
297+
}
298+
265299
return nil
266300
}

0 commit comments

Comments
 (0)