github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/scale.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/docker/cli/cli"
    10  	"github.com/docker/cli/cli/command"
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/versions"
    13  	"github.com/pkg/errors"
    14  	"github.com/spf13/cobra"
    15  )
    16  
    17  type scaleOptions struct {
    18  	detach bool
    19  }
    20  
    21  func newScaleCommand(dockerCli command.Cli) *cobra.Command {
    22  	options := &scaleOptions{}
    23  
    24  	cmd := &cobra.Command{
    25  		Use:   "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]",
    26  		Short: "Scale one or multiple replicated services",
    27  		Args:  scaleArgs,
    28  		RunE: func(cmd *cobra.Command, args []string) error {
    29  			return runScale(cmd.Context(), dockerCli, options, args)
    30  		},
    31  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    32  			return CompletionFn(dockerCli)(cmd, args, toComplete)
    33  		},
    34  	}
    35  
    36  	flags := cmd.Flags()
    37  	addDetachFlag(flags, &options.detach)
    38  	return cmd
    39  }
    40  
    41  func scaleArgs(cmd *cobra.Command, args []string) error {
    42  	if err := cli.RequiresMinArgs(1)(cmd, args); err != nil {
    43  		return err
    44  	}
    45  	for _, arg := range args {
    46  		if k, v, ok := strings.Cut(arg, "="); !ok || k == "" || v == "" {
    47  			return errors.Errorf(
    48  				"Invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage:  %s\n\n%s",
    49  				arg,
    50  				cmd.CommandPath(),
    51  				cmd.UseLine(),
    52  				cmd.Short,
    53  			)
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  func runScale(ctx context.Context, dockerCli command.Cli, options *scaleOptions, args []string) error {
    60  	var errs []string
    61  	var serviceIDs []string
    62  
    63  	for _, arg := range args {
    64  		serviceID, scaleStr, _ := strings.Cut(arg, "=")
    65  
    66  		// validate input arg scale number
    67  		scale, err := strconv.ParseUint(scaleStr, 10, 64)
    68  		if err != nil {
    69  			errs = append(errs, fmt.Sprintf("%s: invalid replicas value %s: %v", serviceID, scaleStr, err))
    70  			continue
    71  		}
    72  
    73  		if err := runServiceScale(ctx, dockerCli, serviceID, scale); err != nil {
    74  			errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
    75  		} else {
    76  			serviceIDs = append(serviceIDs, serviceID)
    77  		}
    78  	}
    79  
    80  	if len(serviceIDs) > 0 {
    81  		if !options.detach && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.29") {
    82  			for _, serviceID := range serviceIDs {
    83  				if err := WaitOnService(ctx, dockerCli, serviceID, false); err != nil {
    84  					errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
    85  				}
    86  			}
    87  		}
    88  	}
    89  
    90  	if len(errs) == 0 {
    91  		return nil
    92  	}
    93  	return errors.Errorf(strings.Join(errs, "\n"))
    94  }
    95  
    96  func runServiceScale(ctx context.Context, dockerCli command.Cli, serviceID string, scale uint64) error {
    97  	client := dockerCli.Client()
    98  
    99  	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	serviceMode := &service.Spec.Mode
   105  	switch {
   106  	case serviceMode.Replicated != nil:
   107  		serviceMode.Replicated.Replicas = &scale
   108  	case serviceMode.ReplicatedJob != nil:
   109  		serviceMode.ReplicatedJob.TotalCompletions = &scale
   110  	default:
   111  		return errors.Errorf("scale can only be used with replicated or replicated-job mode")
   112  	}
   113  
   114  	response, err := client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	for _, warning := range response.Warnings {
   120  		fmt.Fprintln(dockerCli.Err(), warning)
   121  	}
   122  
   123  	fmt.Fprintf(dockerCli.Out(), "%s scaled to %d\n", serviceID, scale)
   124  	return nil
   125  }