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
2 changes: 1 addition & 1 deletion cli/command/service/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
return nil
}

return waitOnService(ctx, dockerCli, response.ID, opts.quiet)
return WaitOnService(ctx, dockerCli, response.ID, opts.quiet)
}

// setConfigs does double duty: it both sets the ConfigReferences of the
Expand Down
4 changes: 2 additions & 2 deletions cli/command/service/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"github.com/docker/docker/pkg/jsonmessage"
)

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

Expand Down
2 changes: 1 addition & 1 deletion cli/command/service/progress/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
if converged && time.Since(convergedAt) >= monitor {
progressOut.WriteProgress(progress.Progress{
ID: "verify",
Action: "Service converged",
Action: fmt.Sprintf("Service %s converged", serviceID),
})
if message != nil {
progressOut.WriteProgress(*message)
Expand Down
2 changes: 1 addition & 1 deletion cli/command/service/rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,5 @@ func runRollback(ctx context.Context, dockerCli command.Cli, options *serviceOpt
return nil
}

return waitOnService(ctx, dockerCli, serviceID, options.quiet)
return WaitOnService(ctx, dockerCli, serviceID, options.quiet)
}
2 changes: 1 addition & 1 deletion cli/command/service/scale.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func runScale(ctx context.Context, dockerCli command.Cli, options *scaleOptions,
if len(serviceIDs) > 0 {
if !options.detach && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.29") {
for _, serviceID := range serviceIDs {
if err := waitOnService(ctx, dockerCli, serviceID, false); err != nil {
if err := WaitOnService(ctx, dockerCli, serviceID, false); err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/command/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
return nil
}

return waitOnService(ctx, dockerCli, serviceID, options.quiet)
return WaitOnService(ctx, dockerCli, serviceID, options.quiet)
}

//nolint:gocyclo
Expand Down
4 changes: 3 additions & 1 deletion cli/command/stack/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
if err != nil {
return err
}
return swarm.RunDeploy(cmd.Context(), dockerCli, opts, config)
return swarm.RunDeploy(cmd.Context(), dockerCli, cmd.Flags(), &opts, config)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
Expand All @@ -42,5 +42,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&opts.ResolveImage, "resolve-image", swarm.ResolveImageAlways,
`Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`", "`+swarm.ResolveImageChanged+`", "`+swarm.ResolveImageNever+`")`)
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
flags.BoolVarP(&opts.Detach, "detach", "d", true, "Exit immediately instead of waiting for the stack services to converge")
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Suppress progress output")
Comment on lines +45 to +46
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this PR (currently at least) keeps the old behavior (docker stack deploy being non-interactive / detached as default).

If the plan is to eventually make "interactive" (--detach=false) the default, I also went looking how we handled the transition for docker service create; moby/moby@21ec12b (moby/moby#31144)

For that we chose to print a warning; wondering if (and if: when? / which release?) we want to do the same for docker stack; inform users that we plan to switch non-detach as default, with the option to return to the old (--detach[=true]) behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a message just like in moby/moby@21ec12b (moby/moby#31144) as you said, indicating that we plan to switch non-detach as default.

return cmd
}
2 changes: 2 additions & 0 deletions cli/command/stack/options/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type Deploy struct {
ResolveImage string
SendRegistryAuth bool
Prune bool
Detach bool
Quiet bool
}

// Config holds docker stack config options
Expand Down
10 changes: 8 additions & 2 deletions cli/command/stack/swarm/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)

// Resolve image constants
Expand All @@ -22,8 +23,8 @@ const (
)

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

if opts.Detach && !flags.Changed("detach") {
fmt.Fprintln(dockerCli.Err(), "Since --detach=false was not specified, tasks will be created in the background.\n"+
"In a future release, --detach=false will become the default.")
}

return deployCompose(ctx, dockerCli, opts, cfg)
}

Expand Down
66 changes: 50 additions & 16 deletions cli/command/stack/swarm/deploy_composefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package swarm

import (
"context"
"errors"
"fmt"

"github.com/docker/cli/cli/command"
servicecli "github.com/docker/cli/cli/command/service"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
composetypes "github.com/docker/cli/cli/compose/types"
Expand All @@ -13,10 +15,9 @@ import (
"github.com/docker/docker/api/types/swarm"
apiclient "github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
)

func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, config *composetypes.Config) error {
func deployCompose(ctx context.Context, dockerCli command.Cli, opts *options.Deploy, config *composetypes.Config) error {
if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
return err
}
Expand Down Expand Up @@ -60,7 +61,17 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
if err != nil {
return err
}
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)

serviceIDs, err := deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
if err != nil {
return err
}

if opts.Detach {
return nil
}

return waitOnServices(ctx, dockerCli, serviceIDs, opts.Quiet)
}

func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
Expand All @@ -87,11 +98,11 @@ func validateExternalNetworks(ctx context.Context, client apiclient.NetworkAPICl
network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
switch {
case errdefs.IsNotFound(err):
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)
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)
case err != nil:
return err
case network.Scope != "swarm":
return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
return fmt.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
}
}
return nil
Expand All @@ -106,13 +117,13 @@ func createSecrets(ctx context.Context, dockerCli command.Cli, secrets []swarm.S
case err == nil:
// secret already exists, then we update that
if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name)
return fmt.Errorf("failed to update secret %s: %w", secretSpec.Name, err)
}
case errdefs.IsNotFound(err):
// secret does not exist, then we create a new one.
fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name)
if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name)
return fmt.Errorf("failed to create secret %s: %w", secretSpec.Name, err)
}
default:
return err
Expand All @@ -130,13 +141,13 @@ func createConfigs(ctx context.Context, dockerCli command.Cli, configs []swarm.C
case err == nil:
// config already exists, then we update that
if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
return fmt.Errorf("failed to update config %s: %w", configSpec.Name, err)
}
case errdefs.IsNotFound(err):
// config does not exist, then we create a new one.
fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
return fmt.Errorf("failed to create config %s: %w", configSpec.Name, err)
}
default:
return err
Expand Down Expand Up @@ -169,26 +180,28 @@ func createNetworks(ctx context.Context, dockerCli command.Cli, namespace conver

fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
return errors.Wrapf(err, "failed to create network %s", name)
return fmt.Errorf("failed to create network %s: %w", name, err)
}
}
return nil
}

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

existingServices, err := getStackServices(ctx, apiClient, namespace.Name())
if err != nil {
return err
return nil, err
}

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

var serviceIDs []string

for internalName, serviceSpec := range services {
var (
name = namespace.Scope(internalName)
Expand All @@ -200,7 +213,7 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
// Retrieve encoded auth token from the image reference
encodedAuth, err = command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), image)
if err != nil {
return err
return nil, err
}
}

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

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

for _, warning := range response.Warnings {
fmt.Fprintln(dockerCli.Err(), warning)
}

serviceIDs = append(serviceIDs, service.ID)
} else {
fmt.Fprintf(out, "Creating service %s\n", name)

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

if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
return errors.Wrapf(err, "failed to create service %s", name)
response, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts)
if err != nil {
return nil, fmt.Errorf("failed to create service %s: %w", name, err)
}

serviceIDs = append(serviceIDs, response.ID)
}
}

return serviceIDs, nil
}

func waitOnServices(ctx context.Context, dockerCli command.Cli, serviceIDs []string, quiet bool) error {
var errs []error
for _, serviceID := range serviceIDs {
if err := servicecli.WaitOnService(ctx, dockerCli, serviceID, quiet); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", serviceID, err))
}
}

if len(errs) > 0 {
return errors.Join(errs...)
}
Comment on lines +295 to +297
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can just return errors.Join(errs...) here, it'll be nil if len(errs) == 0


return nil
}
2 changes: 1 addition & 1 deletion cli/command/stack/swarm/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
},
},
}
err := deployServices(ctx, client, spec, namespace, false, ResolveImageChanged)
_, err := deployServices(ctx, client, spec, namespace, false, ResolveImageChanged)
assert.NilError(t, err)
assert.Check(t, is.Equal(receivedOptions.QueryRegistry, tc.expectedQueryRegistry))
assert.Check(t, is.Equal(receivedService.TaskTemplate.ContainerSpec.Image, tc.expectedImage))
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/commandline/stack_deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ Deploy a new stack or update an existing stack
| Name | Type | Default | Description |
|:---------------------------------------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------|
| [`-c`](#compose-file), [`--compose-file`](#compose-file) | `stringSlice` | | Path to a Compose file, or `-` to read from stdin |
| `-d`, `--detach` | `bool` | `true` | Exit immediately instead of waiting for the stack services to converge |
| `--prune` | | | Prune services that are no longer referenced |
| `-q`, `--quiet` | | | Suppress progress output |
| `--resolve-image` | `string` | `always` | Query the registry to resolve image digest and supported platforms (`always`, `changed`, `never`) |
| `--with-registry-auth` | | | Send registry authentication details to Swarm agents |

Expand Down
3 changes: 3 additions & 0 deletions e2e/stack/testdata/stack-deploy-help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ Aliases:
Options:
-c, --compose-file strings Path to a Compose file, or "-" to read
from stdin
-d, --detach Exit immediately instead of waiting for
the stack services to converge (default true)
--prune Prune services that are no longer referenced
-q, --quiet Suppress progress output
--resolve-image string Query the registry to resolve image digest
and supported platforms ("always",
"changed", "never") (default "always")
Expand Down