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 }