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 }