Skip to content

Commit

Permalink
add scale command as alpha
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Lours <[email protected]>
  • Loading branch information
glours committed Sep 6, 2023
1 parent aeb835a commit f14aa70
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/compose/alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func alphaCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
cmd.AddCommand(
watchCommand(p, backend),
vizCommand(p, backend),
scaleCommand(p, backend),
)
return cmd
}
103 changes: 103 additions & 0 deletions cmd/compose/scale.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
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/pkg/errors"
"golang.org/x/exp/maps"
"strconv"
"strings"

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

type scaleOptions struct {
*ProjectOptions
noDeps bool
}

func scaleCommand(p *ProjectOptions, 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, backend, opts, serviceTuples)
}),
ValidArgsFunction: completeServiceNames(p),
}
flags := scaleCmd.Flags()
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")

return scaleCmd
}

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

if opts.noDeps {
project.ForServices(services, types.IgnoreDependencies)
}

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

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
}
15 changes: 15 additions & 0 deletions docs/reference/compose_alpha_scale.md
Original file line number Diff line number Diff line change
@@ -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-->

2 changes: 2 additions & 0 deletions docs/reference/docker_compose_alpha.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ long: Experimental commands
pname: docker compose
plink: docker_compose.yaml
cname:
- docker compose alpha scale
- docker compose alpha viz
- docker compose alpha watch
clink:
- docker_compose_alpha_scale.yaml
- docker_compose_alpha_viz.yaml
- docker_compose_alpha_watch.yaml
inherited_options:
Expand Down
35 changes: 35 additions & 0 deletions docs/reference/docker_compose_alpha_scale.yaml
Original file line number Diff line number Diff line change
@@ -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

3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
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 @@ -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.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
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,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
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ServiceProxy struct {
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
ScaleFn func(ctx context.Context, project *types.Project, options ScaleOptions) error
interceptors []Interceptor
}

Expand Down Expand Up @@ -97,6 +98,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 @@ -343,6 +345,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
35 changes: 35 additions & 0 deletions pkg/compose/scale.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
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())
}

0 comments on commit f14aa70

Please sign in to comment.