github.com/xeptore/docker-cli@v20.10.14+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(dockerCli, options, args)
    30  		},
    31  	}
    32  
    33  	flags := cmd.Flags()
    34  	addDetachFlag(flags, &options.detach)
    35  	return cmd
    36  }
    37  
    38  func scaleArgs(cmd *cobra.Command, args []string) error {
    39  	if err := cli.RequiresMinArgs(1)(cmd, args); err != nil {
    40  		return err
    41  	}
    42  	for _, arg := range args {
    43  		if parts := strings.SplitN(arg, "=", 2); len(parts) != 2 {
    44  			return errors.Errorf(
    45  				"Invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage:  %s\n\n%s",
    46  				arg,
    47  				cmd.CommandPath(),
    48  				cmd.UseLine(),
    49  				cmd.Short,
    50  			)
    51  		}
    52  	}
    53  	return nil
    54  }
    55  
    56  func runScale(dockerCli command.Cli, options *scaleOptions, args []string) error {
    57  	var errs []string
    58  	var serviceIDs []string
    59  	ctx := context.Background()
    60  
    61  	for _, arg := range args {
    62  		parts := strings.SplitN(arg, "=", 2)
    63  		serviceID, scaleStr := parts[0], parts[1]
    64  
    65  		// validate input arg scale number
    66  		scale, err := strconv.ParseUint(scaleStr, 10, 64)
    67  		if err != nil {
    68  			errs = append(errs, fmt.Sprintf("%s: invalid replicas value %s: %v", serviceID, scaleStr, err))
    69  			continue
    70  		}
    71  
    72  		if err := runServiceScale(ctx, dockerCli, serviceID, scale); err != nil {
    73  			errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
    74  		} else {
    75  			serviceIDs = append(serviceIDs, serviceID)
    76  		}
    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  	if serviceMode.Replicated != nil {
   106  		serviceMode.Replicated.Replicas = &scale
   107  	} else if serviceMode.ReplicatedJob != nil {
   108  		serviceMode.ReplicatedJob.TotalCompletions = &scale
   109  	} else {
   110  		return errors.Errorf("scale can only be used with replicated or replicated-job mode")
   111  	}
   112  
   113  	response, err := client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	for _, warning := range response.Warnings {
   119  		fmt.Fprintln(dockerCli.Err(), warning)
   120  	}
   121  
   122  	fmt.Fprintf(dockerCli.Out(), "%s scaled to %d\n", serviceID, scale)
   123  	return nil
   124  }