Skip to content

Commit

Permalink
add scale command
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
  • Loading branch information
glours committed Sep 7, 2023
1 parent d7b0b2b commit 2c86e64
Show file tree
Hide file tree
Showing 15 changed files with 403 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/compose/compose.go
Expand Up @@ -476,6 +476,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
createCommand(&opts, dockerCli, backend),
copyCommand(&opts, dockerCli, backend),
waitCommand(&opts, dockerCli, backend),
scaleCommand(&opts, dockerCli, backend),
alphaCommand(&opts, dockerCli, backend),
)

Expand Down
108 changes: 108 additions & 0 deletions cmd/compose/scale.go
@@ -0,0 +1,108 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package compose

import (
"context"
"strconv"
"strings"

"github.com/docker/cli/cli/command"

"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
"golang.org/x/exp/maps"

"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)

type scaleOptions struct {
*ProjectOptions
noDeps bool
}

func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
opts := scaleOptions{
ProjectOptions: p,
}
scaleCmd := &cobra.Command{
Use: "scale [SERVICE=REPLICAS...]",
Short: "Scale services ",
Args: cobra.MinimumNArgs(1),
RunE: Adapt(func(ctx context.Context, args []string) error {
serviceTuples, err := parseServicesReplicasArgs(args)
if err != nil {
return err
}
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := scaleCmd.Flags()
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")

return scaleCmd
}

func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
services := maps.Keys(serviceReplicaTuples)
project, err := opts.ToProject(dockerCli, services)
if err != nil {
return err
}

if opts.noDeps {
if err := project.ForServices(services, types.IgnoreDependencies); err != nil {
return err
}
}

for key, value := range serviceReplicaTuples {
for i, service := range project.Services {
if service.Name != key {
continue
}
if service.Deploy == nil {
service.Deploy = &types.DeployConfig{}
}
scale := uint64(value)
service.Deploy.Replicas = &scale
project.Services[i] = service
break
}
}

return backend.Scale(ctx, project, api.ScaleOptions{Services: services})
}

func parseServicesReplicasArgs(args []string) (map[string]int, error) {
serviceReplicaTuples := map[string]int{}
for _, arg := range args {
key, val, ok := strings.Cut(arg, "=")
if !ok || key == "" || val == "" {
return nil, errors.Errorf("Invalide scale specifier %q.", arg)
}
intValue, err := strconv.Atoi(val)

if err != nil {
return nil, errors.Errorf("Invalide scale specifier, can't parse replicate value to int %q.", arg)
}
serviceReplicaTuples[key] = intValue
}
return serviceReplicaTuples, nil
}
1 change: 1 addition & 0 deletions docs/reference/compose.md
Expand Up @@ -26,6 +26,7 @@ Define and run multi-container applications with Docker.
| [`restart`](compose_restart.md) | Restart service containers |
| [`rm`](compose_rm.md) | Removes stopped service containers |
| [`run`](compose_run.md) | Run a one-off command on a service. |
| [`scale`](compose_scale.md) | Scale services |
| [`start`](compose_start.md) | Start services |
| [`stop`](compose_stop.md) | Stop services |
| [`top`](compose_top.md) | Display the running processes |
Expand Down
15 changes: 15 additions & 0 deletions docs/reference/compose_alpha_scale.md
@@ -0,0 +1,15 @@
# docker compose alpha scale

<!---MARKER_GEN_START-->
Scale services

### Options

| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--no-deps` | | | Don't start linked services. |


<!---MARKER_GEN_END-->

15 changes: 15 additions & 0 deletions docs/reference/compose_scale.md
@@ -0,0 +1,15 @@
# docker compose scale

<!---MARKER_GEN_START-->
Scale services

### Options

| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--no-deps` | | | Don't start linked services. |


<!---MARKER_GEN_END-->

2 changes: 2 additions & 0 deletions docs/reference/docker_compose.yaml
Expand Up @@ -165,6 +165,7 @@ cname:
- docker compose restart
- docker compose rm
- docker compose run
- docker compose scale
- docker compose start
- docker compose stop
- docker compose top
Expand Down Expand Up @@ -192,6 +193,7 @@ clink:
- docker_compose_restart.yaml
- docker_compose_rm.yaml
- docker_compose_run.yaml
- docker_compose_scale.yaml
- docker_compose_start.yaml
- docker_compose_stop.yaml
- docker_compose_top.yaml
Expand Down
35 changes: 35 additions & 0 deletions docs/reference/docker_compose_alpha_scale.yaml
@@ -0,0 +1,35 @@
command: docker compose alpha scale
short: Scale services
long: Scale services
usage: docker compose alpha scale [SERVICE=REPLICAS...]
pname: docker compose alpha
plink: docker_compose_alpha.yaml
options:
- option: no-deps
value_type: bool
default_value: "false"
description: Don't start linked services.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: dry-run
value_type: bool
default_value: "false"
description: Execute command in dry run mode
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: true
kubernetes: false
swarm: false

35 changes: 35 additions & 0 deletions docs/reference/docker_compose_scale.yaml
@@ -0,0 +1,35 @@
command: docker compose scale
short: Scale services
long: Scale services
usage: docker compose scale [SERVICE=REPLICAS...]
pname: docker compose
plink: docker_compose.yaml
options:
- option: no-deps
value_type: bool
default_value: "false"
description: Don't start linked services.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: dry-run
value_type: bool
default_value: "false"
description: Execute command in dry run mode
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false

3 changes: 2 additions & 1 deletion go.mod
Expand Up @@ -50,6 +50,8 @@ require (
gotest.tools/v3 v3.5.0
)

require golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1

require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
Expand Down Expand Up @@ -156,7 +158,6 @@ require (
go.opentelemetry.io/otel/metric v0.37.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/api.go
Expand Up @@ -88,6 +88,12 @@ type Service interface {
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
// Wait blocks until at least one of the services' container exits
Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error)
// Scale manages numbers of container instances running per service
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
}

type ScaleOptions struct {
Services []string
}

type WaitOptions struct {
Expand Down
9 changes: 9 additions & 0 deletions pkg/api/proxy.go
Expand Up @@ -56,6 +56,7 @@ type ServiceProxy struct {
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
PublishFn func(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
ScaleFn func(ctx context.Context, project *types.Project, options ScaleOptions) error
interceptors []Interceptor
}

Expand Down Expand Up @@ -99,6 +100,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
s.DryRunModeFn = service.DryRunMode
s.VizFn = service.Viz
s.WaitFn = service.Wait
s.ScaleFn = service.Scale
return s
}

Expand Down Expand Up @@ -349,6 +351,13 @@ func (s *ServiceProxy) Wait(ctx context.Context, projectName string, options Wai
return s.WaitFn(ctx, projectName, options)
}

func (s *ServiceProxy) Scale(ctx context.Context, project *types.Project, options ScaleOptions) error {
if s.ScaleFn == nil {
return ErrNotImplemented
}
return s.ScaleFn(ctx, project, options)
}

func (s *ServiceProxy) MaxConcurrency(i int) {
s.MaxConcurrencyFn(i)
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/compose/scale.go
@@ -0,0 +1,36 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose

import (
"context"

"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/internal/tracing"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
)

func (s *composeService) Scale(ctx context.Context, project *types.Project, options api.ScaleOptions) error {
return progress.Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(project), func(ctx context.Context) error {
err := s.create(ctx, project, api.CreateOptions{Services: options.Services})
if err != nil {
return err
}
return s.start(ctx, project.Name, api.StartOptions{Project: project, Services: options.Services}, nil)

}), s.stdinfo())
}
15 changes: 15 additions & 0 deletions pkg/e2e/fixtures/scale/compose.yaml
@@ -0,0 +1,15 @@
services:
back:
image: nginx:alpine
depends_on:
- db
db:
image: nginx:alpine
front:
image: nginx:alpine
deploy:
replicas: 2
dbadmin:
image: nginx:alpine
deploy:
replicas: 0

0 comments on commit 2c86e64

Please sign in to comment.