Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add scale command #10979

Merged
merged 2 commits into from Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}

Check warning on line 51 in cmd/compose/scale.go

View check run for this annotation

Codecov / codecov/patch

cmd/compose/scale.go#L50-L51

Added lines #L50 - L51 were not covered by tests
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
}

Check warning on line 67 in cmd/compose/scale.go

View check run for this annotation

Codecov / codecov/patch

cmd/compose/scale.go#L66-L67

Added lines #L66 - L67 were not covered by tests

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

Check warning on line 72 in cmd/compose/scale.go

View check run for this annotation

Codecov / codecov/patch

cmd/compose/scale.go#L71-L72

Added lines #L71 - L72 were not covered by tests
}

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("invalid scale specifier: %s", arg)
}

Check warning on line 99 in cmd/compose/scale.go

View check run for this annotation

Codecov / codecov/patch

cmd/compose/scale.go#L98-L99

Added lines #L98 - L99 were not covered by tests
intValue, err := strconv.Atoi(val)

if err != nil {
return nil, errors.Errorf("invalid scale specifier: can't parse replica value as int: %v", arg)
}

Check warning on line 104 in cmd/compose/scale.go

View check run for this annotation

Codecov / codecov/patch

cmd/compose/scale.go#L103-L104

Added lines #L103 - L104 were not covered by tests
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 @@ -51,6 +51,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 @@ -155,7 +157,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.11.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.12.0 // indirect
golang.org/x/oauth2 v0.10.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 @@
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 @@
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 @@
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
}

Check warning on line 357 in pkg/api/proxy.go

View check run for this annotation

Codecov / codecov/patch

pkg/api/proxy.go#L356-L357

Added lines #L356 - L357 were not covered by tests
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
}

Check warning on line 32 in pkg/compose/scale.go

View check run for this annotation

Codecov / codecov/patch

pkg/compose/scale.go#L31-L32

Added lines #L31 - L32 were not covered by tests
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