2016-09-08 13:11:39 -04:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
2018-05-03 18:02:44 -07:00
|
|
|
"context"
|
2025-02-03 19:46:59 +01:00
|
|
|
"errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2025-05-19 17:03:39 +02:00
|
|
|
"github.com/docker/docker/api/types/swarm"
|
Use non-detached mode as default for service commands
Commit 330a0035334871d92207b583c1c36d52a244753f added a `--detach=false` option
to various service-related commands, with the intent to make this the default in
a future version (17.09).
This patch changes the default to use "interactive" (non-detached), allowing
users to override this by setting the `--detach` option.
To prevent problems when connecting to older daemon versions (17.05 and below,
see commit db60f255617fd90cb093813dcdfe7eec840eeff8), the detach option is
ignored for those versions, and detach is always true.
Before this change, a warning was printed to announce the upcoming default:
$ docker service create nginx:alpine
saxiyn3pe559d753730zr0xer
Since --detach=false was not specified, tasks will be created in the background.
In a future release, --detach=false will become the default.
After this change, no warning is printed, but `--detach` is disabled;
$ docker service create nginx:alpine
y9jujwzozi0hwgj5yaadzliq6
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
Setting the `--detach` flag makes the cli use the pre-17.06 behavior:
$ docker service create --detach nginx:alpine
280hjnzy0wzje5o56gr22a46n
Running against a 17.03 daemon, without specifying the `--detach` flag;
$ docker service create nginx:alpine
kqheg7ogj0kszoa34g4p73i8q
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-09-12 14:35:19 +02:00
|
|
|
"github.com/docker/docker/api/types/versions"
|
2025-02-03 19:46:59 +01:00
|
|
|
"github.com/docker/docker/client"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2017-06-27 09:55:10 -07:00
|
|
|
type scaleOptions struct {
|
|
|
|
detach bool
|
|
|
|
}
|
|
|
|
|
2017-07-20 10:32:51 +02:00
|
|
|
func newScaleCommand(dockerCli command.Cli) *cobra.Command {
|
2017-06-27 09:55:10 -07:00
|
|
|
options := &scaleOptions{}
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
2016-09-08 13:11:39 -04:00
|
|
|
Use: "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]",
|
2016-11-02 15:53:18 +08:00
|
|
|
Short: "Scale one or multiple replicated services",
|
2016-09-08 13:11:39 -04:00
|
|
|
Args: scaleArgs,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2023-09-09 22:27:44 +00:00
|
|
|
return runScale(cmd.Context(), dockerCli, options, args)
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
2025-03-25 21:38:21 +00:00
|
|
|
ValidArgsFunction: completeScaleArgs(dockerCli),
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-06-27 09:55:10 -07:00
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
addDetachFlag(flags, &options.detach)
|
|
|
|
return cmd
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func scaleArgs(cmd *cobra.Command, args []string) error {
|
|
|
|
if err := cli.RequiresMinArgs(1)(cmd, args); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, arg := range args {
|
2022-12-27 16:27:13 +01:00
|
|
|
if k, v, ok := strings.Cut(arg, "="); !ok || k == "" || v == "" {
|
2025-02-03 19:46:59 +01:00
|
|
|
return fmt.Errorf(
|
|
|
|
"invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
|
2016-09-08 13:11:39 -04:00
|
|
|
arg,
|
|
|
|
cmd.CommandPath(),
|
|
|
|
cmd.UseLine(),
|
|
|
|
cmd.Short,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2025-02-03 19:46:59 +01:00
|
|
|
func runScale(ctx context.Context, dockerCLI command.Cli, options *scaleOptions, args []string) error {
|
|
|
|
apiClient := dockerCLI.Client()
|
|
|
|
var (
|
|
|
|
errs []error
|
|
|
|
serviceIDs = make([]string, 0, len(args))
|
|
|
|
)
|
2016-09-08 13:11:39 -04:00
|
|
|
for _, arg := range args {
|
2022-12-27 16:27:13 +01:00
|
|
|
serviceID, scaleStr, _ := strings.Cut(arg, "=")
|
2016-09-19 16:40:44 +08:00
|
|
|
|
|
|
|
// validate input arg scale number
|
|
|
|
scale, err := strconv.ParseUint(scaleStr, 10, 64)
|
|
|
|
if err != nil {
|
2025-02-03 19:46:59 +01:00
|
|
|
errs = append(errs, fmt.Errorf("%s: invalid replicas value %s: %v", serviceID, scaleStr, err))
|
2016-09-19 16:40:44 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-02-03 19:46:59 +01:00
|
|
|
warnings, err := runServiceScale(ctx, apiClient, serviceID, scale)
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("%s: %v", serviceID, err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, warning := range warnings {
|
|
|
|
_, _ = fmt.Fprintln(dockerCLI.Err(), warning)
|
2017-06-27 09:55:10 -07:00
|
|
|
}
|
2025-02-03 19:46:59 +01:00
|
|
|
_, _ = fmt.Fprintf(dockerCLI.Out(), "%s scaled to %d\n", serviceID, scale)
|
|
|
|
serviceIDs = append(serviceIDs, serviceID)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2025-02-03 19:46:59 +01:00
|
|
|
if len(serviceIDs) > 0 && !options.detach && versions.GreaterThanOrEqualTo(dockerCLI.Client().ClientVersion(), "1.29") {
|
|
|
|
for _, serviceID := range serviceIDs {
|
|
|
|
if err := WaitOnService(ctx, dockerCLI, serviceID, false); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("%s: %v", serviceID, err))
|
2017-06-30 12:04:49 +02:00
|
|
|
}
|
|
|
|
}
|
2017-06-27 09:55:10 -07:00
|
|
|
}
|
2025-02-03 19:46:59 +01:00
|
|
|
return errors.Join(errs...)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2025-02-03 19:46:59 +01:00
|
|
|
func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string, scale uint64) (warnings []string, _ error) {
|
2025-05-19 17:03:39 +02:00
|
|
|
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2025-02-03 19:46:59 +01:00
|
|
|
return nil, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
serviceMode := &service.Spec.Mode
|
2023-11-20 13:54:53 +01:00
|
|
|
switch {
|
|
|
|
case serviceMode.Replicated != nil:
|
2020-01-09 12:17:43 -06:00
|
|
|
serviceMode.Replicated.Replicas = &scale
|
2023-11-20 13:54:53 +01:00
|
|
|
case serviceMode.ReplicatedJob != nil:
|
2020-01-09 12:17:43 -06:00
|
|
|
serviceMode.ReplicatedJob.TotalCompletions = &scale
|
2023-11-20 13:54:53 +01:00
|
|
|
default:
|
2025-02-03 19:46:59 +01:00
|
|
|
return nil, errors.New("scale can only be used with replicated or replicated-job mode")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2016-09-19 16:40:44 +08:00
|
|
|
|
2025-05-19 17:03:39 +02:00
|
|
|
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, swarm.ServiceUpdateOptions{})
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
2025-02-03 19:46:59 +01:00
|
|
|
return nil, err
|
2016-11-14 18:08:24 -08:00
|
|
|
}
|
2025-02-03 19:46:59 +01:00
|
|
|
return response.Warnings, nil
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2025-03-25 21:38:21 +00:00
|
|
|
|
|
|
|
// completeScaleArgs returns a completion function for the args of the scale command.
|
|
|
|
// It completes service names followed by "=", suppressing the trailing space.
|
|
|
|
func completeScaleArgs(dockerCli command.Cli) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
// reuse the existing logic for configurable completion of service names and IDs.
|
2025-03-27 09:55:01 +01:00
|
|
|
completions, directive := completeServiceNames(dockerCli)(cmd, args, toComplete)
|
2025-03-25 21:38:21 +00:00
|
|
|
if directive == cobra.ShellCompDirectiveError {
|
|
|
|
return completions, directive
|
|
|
|
}
|
|
|
|
for i, v := range completions {
|
|
|
|
completions[i] = v + "="
|
|
|
|
}
|
|
|
|
return completions, directive | cobra.ShellCompDirectiveNoSpace
|
|
|
|
}
|
|
|
|
}
|