Skip to content

Commit

Permalink
Merge pull request #602 from Junnplus/compose-up-scale
Browse files Browse the repository at this point in the history
add scale flag for compose up
  • Loading branch information
AkihiroSuda authored Dec 14, 2021
2 parents e81f1ca + 7cccf41 commit b72b5ca
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 81 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1093,10 +1093,10 @@ Flags:
- :whale: `--build`: Build images before starting containers.
- :nerd_face: `--ipfs`: Build images with pulling base images from IPFS. See [`./docs/ipfs.md`](./docs/ipfs.md) for details.
- :whale: `--quiet-pull`: Pull without printing progress information
- :whale: `--scale`: Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.

Unimplemented `docker-compose up` (V1) flags: `--no-deps`, `--force-recreate`, `--always-recreate-deps`, `--no-recreate`,
`--no-start`, `--abort-on-container-exit`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--remove-orphans`, `--exit-code-from`,
`--scale`
`--no-start`, `--abort-on-container-exit`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--remove-orphans`, `--exit-code-from`

Unimplemented `docker compose up` (V2) flags: `--environment`

Expand Down
2 changes: 1 addition & 1 deletion cmd/nerdctl/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,5 @@ func getComposer(cmd *cobra.Command, client *containerd.Client) (*composer.Compo
return imgErr
}

return composer.New(o)
return composer.New(o, client)
}
9 changes: 1 addition & 8 deletions cmd/nerdctl/compose_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
package main

import (
"fmt"

"github.com/containerd/nerdctl/pkg/composer"
"github.com/spf13/cobra"
)
Expand All @@ -27,7 +25,6 @@ func newComposeLogsCommand() *cobra.Command {
var composeLogsCommand = &cobra.Command{
Use: "logs",
Short: "Show logs of a running container",
Args: cobra.NoArgs,
RunE: composeLogsAction,
SilenceUsage: true,
SilenceErrors: true,
Expand All @@ -41,10 +38,6 @@ func newComposeLogsCommand() *cobra.Command {
}

func composeLogsAction(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
// TODO: support specifying service names as args
return fmt.Errorf("arguments %v not supported", args)
}
follow, err := cmd.Flags().GetBool("follow")
if err != nil {
return err
Expand Down Expand Up @@ -83,5 +76,5 @@ func composeLogsAction(cmd *cobra.Command, args []string) error {
NoColor: noColor,
NoLogPrefix: noLogPrefix,
}
return c.Logs(ctx, lo)
return c.Logs(ctx, lo, args)
}
68 changes: 32 additions & 36 deletions cmd/nerdctl/compose_ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ func composePsAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
services, err := c.Services(ctx)
if err != nil {
return err
}

// TODO: make JSON-printable.
// The JSON format must correspond to `docker compose ps --json` (Docker Compose v2)
Expand All @@ -63,39 +59,39 @@ func composePsAction(cmd *cobra.Command, args []string) error {
Ports string
}

var containersPrintable []containerPrintable

for _, svc := range services {
for _, container := range svc.Containers {
containersGot, err := client.Containers(ctx, fmt.Sprintf("labels.%q==%s", labels.Name, container.Name))
if err != nil {
return err
}
if len(containersGot) != 1 {
return fmt.Errorf("expected 1 container, got %d", len(containersGot))
}
info, err := containersGot[0].Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return err
}

spec, err := containersGot[0].Spec(ctx)
if err != nil {
return err
}
status := formatter.ContainerStatus(ctx, containersGot[0])
if status == "Up" {
status = "running" // corresponds to Docker Compose v2.0.1
}
p := containerPrintable{
Name: container.Name,
Command: formatter.InspectContainerCommandTrunc(spec),
Service: svc.Unparsed.Name,
Status: status,
Ports: formatter.FormatPorts(info.Labels),
}
containersPrintable = append(containersPrintable, p)
containersPrintable := []containerPrintable{}

serviceNames, err := c.ServiceNames(args...)
if err != nil {
return err
}

containers, err := c.Containers(ctx, serviceNames...)
if err != nil {
return err
}
for _, container := range containers {
info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
return err
}

spec, err := container.Spec(ctx)
if err != nil {
return err
}
status := formatter.ContainerStatus(ctx, container)
if status == "Up" {
status = "running" // corresponds to Docker Compose v2.0.1
}
p := containerPrintable{
Name: info.Labels[labels.Name],
Command: formatter.InspectContainerCommandTrunc(spec),
Service: info.Labels[labels.ComposeService],
Status: status,
Ports: formatter.FormatPorts(info.Labels),
}
containersPrintable = append(containersPrintable, p)
}

w := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
Expand Down
21 changes: 21 additions & 0 deletions cmd/nerdctl/compose_up.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package main

import (
"errors"
"fmt"
"strconv"
"strings"

"github.com/containerd/nerdctl/pkg/composer"
"github.com/spf13/cobra"
Expand All @@ -38,6 +41,7 @@ func newComposeUpCommand() *cobra.Command {
composeUpCommand.Flags().Bool("build", false, "Build images before starting containers.")
composeUpCommand.Flags().Bool("ipfs", false, "Allow pulling base images from IPFS during build")
composeUpCommand.Flags().Bool("quiet-pull", false, "Pull without printing progress information")
composeUpCommand.Flags().StringArray("scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
return composeUpCommand
}

Expand Down Expand Up @@ -73,6 +77,22 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
if err != nil {
return err
}
scaleSlice, err := cmd.Flags().GetStringArray("scale")
if err != nil {
return err
}
scale := make(map[string]uint64)
for _, s := range scaleSlice {
parts := strings.Split(s, "=")
if len(parts) != 2 {
return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", s)
}
replicas, err := strconv.Atoi(parts[1])
if err != nil {
return err
}
scale[parts[0]] = uint64(replicas)
}

client, ctx, cancel, err := newClient(cmd)
if err != nil {
Expand All @@ -92,6 +112,7 @@ func composeUpAction(cmd *cobra.Command, services []string) error {
ForceBuild: build,
IPFS: enableIPFS,
QuietPull: quietPull,
Scale: scale,
}
return c.Up(ctx, uo, services)
}
24 changes: 24 additions & 0 deletions cmd/nerdctl/compose_up_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,27 @@ services:
base.ComposeCmd("-f", comp.YAMLFullPath(), "--env-file", "envFile", "up", "-d").AssertFail()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
}

func TestComposeUpWithScale(t *testing.T) {
base := testutil.NewBase(t)

var dockerComposeYAML = fmt.Sprintf(`
version: '3.1'
services:
test:
image: %s
command: "sleep infinity"
`, testutil.AlpineImage)

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()

projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)

base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "--scale", "test=2").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()

base.ComposeCmd("-f", comp.YAMLFullPath(), "ps").AssertOutContains(fmt.Sprintf("%s_test_2", projectName))
}
16 changes: 15 additions & 1 deletion pkg/composer/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
composecli "github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
compose "github.com/compose-spec/compose-go/types"
"github.com/containerd/containerd"
"github.com/containerd/containerd/identifiers"
"github.com/containerd/nerdctl/pkg/composer/projectloader"
"github.com/containerd/nerdctl/pkg/composer/serviceparser"
Expand All @@ -50,7 +51,7 @@ type Options struct {
DebugPrintFull bool // full debug print, may leak secret env var to logs
}

func New(o Options) (*Composer, error) {
func New(o Options, client *containerd.Client) (*Composer, error) {
if o.NerdctlCmd == "" {
return nil, errors.New("got empty nerdctl cmd")
}
Expand Down Expand Up @@ -118,6 +119,7 @@ func New(o Options) (*Composer, error) {
c := &Composer{
Options: o,
project: project,
client: client,
}

return c, nil
Expand All @@ -126,6 +128,7 @@ func New(o Options) (*Composer, error) {
type Composer struct {
Options
project *compose.Project
client *containerd.Client
}

func (c *Composer) createNerdctlCmd(ctx context.Context, args ...string) *exec.Cmd {
Expand Down Expand Up @@ -181,3 +184,14 @@ func (c *Composer) Services(ctx context.Context) ([]*serviceparser.Service, erro
}
return services, nil
}

func (c *Composer) ServiceNames(services ...string) ([]string, error) {
var names []string
if err := c.project.WithServices(services, func(svc types.ServiceConfig) error {
names = append(names, svc.Name)
return nil
}); err != nil {
return nil, err
}
return names, nil
}
43 changes: 43 additions & 0 deletions pkg/composer/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright The containerd 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 composer

import (
"context"
"fmt"

"github.com/containerd/containerd"
"github.com/containerd/nerdctl/pkg/labels"
"github.com/sirupsen/logrus"
)

func (c *Composer) Containers(ctx context.Context, services ...string) ([]containerd.Container, error) {
projectLabel := fmt.Sprintf("labels.%q==%s", labels.ComposeProject, c.project.Name)
filters := []string{}
for _, service := range services {
filters = append(filters, fmt.Sprintf("%s,labels.%q==%s", projectLabel, labels.ComposeService, service))
}
if len(services) == 0 {
filters = append(filters, projectLabel)
}
logrus.Debugf("filters: %v", filters)
containers, err := c.client.Containers(ctx, filters...)
if err != nil {
return nil, err
}
return containers, nil
}
31 changes: 19 additions & 12 deletions pkg/composer/down.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import (
"context"
"fmt"

compose "github.com/compose-spec/compose-go/types"
"github.com/containerd/nerdctl/pkg/composer/serviceparser"
"github.com/containerd/containerd"
"github.com/containerd/nerdctl/pkg/labels"
"github.com/containerd/nerdctl/pkg/strutil"

"github.com/sirupsen/logrus"
)
Expand All @@ -31,8 +32,17 @@ type DownOptions struct {
}

func (c *Composer) Down(ctx context.Context, downOptions DownOptions) error {
for _, svc := range c.project.Services {
if err := c.downService(ctx, svc, downOptions.RemoveVolumes); err != nil {
serviceNames, err := c.ServiceNames()
if err != nil {
return err
}
// reverse dependency order
for _, svc := range strutil.ReverseStrSlice(serviceNames) {
containers, err := c.Containers(ctx, svc)
if err != nil {
return err
}
if err := c.downContainers(ctx, containers, downOptions.RemoveVolumes); err != nil {
return err
}
}
Expand Down Expand Up @@ -100,18 +110,15 @@ func (c *Composer) downVolume(ctx context.Context, shortName string) error {
return nil
}

func (c *Composer) downService(ctx context.Context, svc compose.ServiceConfig, removeAnonVolumes bool) error {
ps, err := serviceparser.Parse(c.project, svc)
if err != nil {
return err
}
for _, container := range ps.Containers {
logrus.Infof("Removing container %s", container.Name)
func (c *Composer) downContainers(ctx context.Context, containers []containerd.Container, removeAnonVolumes bool) error {
for _, container := range containers {
info, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)
logrus.Infof("Removing container %s", info.Labels[labels.Name])
args := []string{"rm", "-f"}
if removeAnonVolumes {
args = append(args, "-v")
}
args = append(args, container.Name)
args = append(args, container.ID())
if err := c.runNerdctlCmd(ctx, args...); err != nil {
logrus.Warn(err)
}
Expand Down
Loading

0 comments on commit b72b5ca

Please sign in to comment.