Handle service scale with container numbering

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-11-17 15:15:27 +01:00
parent da99ad40d5
commit 2278370ffa
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
3 changed files with 128 additions and 32 deletions

View File

@ -73,3 +73,4 @@ func (s *local) ResourceService() resources.Service {
} }

View File

@ -22,8 +22,10 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"golang.org/x/sync/errgroup"
"io" "io"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
@ -169,13 +171,42 @@ func (s *local) Down(ctx context.Context, projectName string) error {
if err != nil { if err != nil {
return err return err
} }
eg, ctx := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
for _, c := range list { for _, c := range list {
err := s.containerService.Stop(ctx, c.ID, nil) container := c
eg.Go(func() error {
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Stopping",
Status: progress.Working,
Done: false,
})
err := s.containerService.Stop(ctx, container.ID, nil)
if err != nil { if err != nil {
return err return err
} }
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Removing",
Status: progress.Working,
Done: false,
})
err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
if err != nil {
return err
} }
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Removed",
Status: progress.Done,
Done: true,
})
return nil return nil
})
}
return eg.Wait()
} }
func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error { func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error {
@ -250,7 +281,7 @@ func (s *local) Convert(ctx context.Context, project *types.Project, format stri
} }
} }
func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
hash, err := jsonHash(s) hash, err := jsonHash(s)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -259,6 +290,7 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, inherit
"com.docker.compose.project": p.Name, "com.docker.compose.project": p.Name,
"com.docker.compose.service": s.Name, "com.docker.compose.service": s.Name,
"com.docker.compose.config-hash": hash, "com.docker.compose.config-hash": hash,
"com.docker.compose.container-number": strconv.Itoa(number),
} }
var ( var (
@ -523,7 +555,7 @@ func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error
} }
w.Event(progress.Event{ w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", n.Name), ID: fmt.Sprintf("Network %q", n.Name),
Status: progress.Working, Status: progress.Done,
StatusText: "Created", StatusText: "Created",
Done: true, Done: true,
}) })

View File

@ -21,14 +21,14 @@ package local
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/docker/docker/api/types/network" "github.com/compose-spec/compose-go/types"
"github.com/docker/compose-cli/api/containers" "github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/progress" "github.com/docker/compose-cli/progress"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"golang.org/x/sync/errgroup"
"strconv"
) )
func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error { func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
@ -42,30 +42,90 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
return err return err
} }
scale := getScale(service)
eg, ctx := errgroup.WithContext(ctx)
if len(actual) < scale {
next, err := nextContainerNumber(actual)
if err != nil {
return err
}
missing := scale - len(actual)
for i := 0; i < missing; i++ {
number := next + i
name := fmt.Sprintf("%s_%s_%d", project.Name, service.Name, number)
eg.Go(func() error {
return s.createContainer(ctx, project, service, name, number)
})
}
}
if len(actual) > scale {
for i := scale; i < len(actual); i++ {
container := actual[i]
eg.Go(func() error {
err := s.containerService.Stop(ctx, container.ID, nil)
if err != nil {
return err
}
return s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
})
}
actual = actual[:scale]
}
expected, err := jsonHash(service) expected, err := jsonHash(service)
if err != nil { if err != nil {
return err return err
} }
for _, container := range actual {
if len(actual) == 0 { container := container
return s.createContainer(ctx, project, service)
}
container := actual[0] // TODO handle services with replicas
diverged := container.Labels["com.docker.compose.config-hash"] != expected diverged := container.Labels["com.docker.compose.config-hash"] != expected
if diverged { if diverged {
eg.Go(func() error {
return s.recreateContainer(ctx, project, service, container) return s.recreateContainer(ctx, project, service, container)
})
continue
} }
if container.State == "running" { if container.State == "running" {
// already running, skip // already running, skip
return nil continue
} }
eg.Go(func() error {
return s.restartContainer(ctx, service, container) return s.restartContainer(ctx, service, container)
})
}
return eg.Wait()
} }
func (s *local) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig) error { func nextContainerNumber(containers []moby.Container) (int, error) {
max := 0
for _, c := range containers {
n, err := strconv.Atoi(c.Labels["com.docker.compose.container-number"])
if err != nil {
return 0, err
}
if n > max {
max = n
}
}
return max + 1, nil
}
func getScale(config types.ServiceConfig) int {
if config.Deploy != nil && config.Deploy.Replicas != nil {
return int(*config.Deploy.Replicas)
}
if config.Scale != 0 {
return config.Scale
}
return 1
}
func (s *local) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int) error {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
w.Event(progress.Event{ w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name), ID: fmt.Sprintf("Service %q", service.Name),
@ -73,8 +133,7 @@ func (s *local) createContainer(ctx context.Context, project *types.Project, ser
StatusText: "Create", StatusText: "Create",
Done: false, Done: false,
}) })
name := fmt.Sprintf("%s_%s", project.Name, service.Name) err := s.runContainer(ctx, project, service, name, number, nil)
err := s.runContainer(ctx, project, service, name, nil)
if err != nil { if err != nil {
return err return err
} }
@ -105,7 +164,11 @@ func (s *local) recreateContainer(ctx context.Context, project *types.Project, s
if err != nil { if err != nil {
return err return err
} }
err = s.runContainer(ctx, project, service, name, &container) number, err := strconv.Atoi(container.Labels["com.docker.compose.container-number"])
if err != nil {
return err
}
err = s.runContainer(ctx, project, service, name, number, &container)
if err != nil { if err != nil {
return err return err
} }
@ -143,8 +206,8 @@ func (s *local) restartContainer(ctx context.Context, service types.ServiceConfi
return nil return nil
} }
func (s *local) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, container *moby.Container) error { func (s *local) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container) error {
containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, container) containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, number, container)
if err != nil { if err != nil {
return err return err
} }