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"
"encoding/json"
"fmt"
"golang.org/x/sync/errgroup"
"io"
"path/filepath"
"strconv"
"strings"
"sync"
@ -169,13 +171,42 @@ func (s *local) Down(ctx context.Context, projectName string) error {
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
for _, c := range list {
err := s.containerService.Stop(ctx, c.ID, nil)
if err != nil {
return err
}
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 {
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 {
@ -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)
if err != nil {
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.service": s.Name,
"com.docker.compose.config-hash": hash,
"com.docker.compose.container-number": strconv.Itoa(number),
}
var (
@ -523,7 +555,7 @@ func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error
}
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", n.Name),
Status: progress.Working,
Status: progress.Done,
StatusText: "Created",
Done: true,
})

View File

@ -21,14 +21,14 @@ package local
import (
"context"
"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/progress"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"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 {
@ -42,30 +42,90 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
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)
if err != nil {
return err
}
for _, container := range actual {
container := container
diverged := container.Labels["com.docker.compose.config-hash"] != expected
if diverged {
eg.Go(func() error {
return s.recreateContainer(ctx, project, service, container)
})
continue
}
if len(actual) == 0 {
return s.createContainer(ctx, project, service)
if container.State == "running" {
// already running, skip
continue
}
eg.Go(func() error {
return s.restartContainer(ctx, service, container)
})
}
container := actual[0] // TODO handle services with replicas
diverged := container.Labels["com.docker.compose.config-hash"] != expected
if diverged {
return s.recreateContainer(ctx, project, service, container)
}
if container.State == "running" {
// already running, skip
return nil
}
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.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
@ -73,8 +133,7 @@ func (s *local) createContainer(ctx context.Context, project *types.Project, ser
StatusText: "Create",
Done: false,
})
name := fmt.Sprintf("%s_%s", project.Name, service.Name)
err := s.runContainer(ctx, project, service, name, nil)
err := s.runContainer(ctx, project, service, name, number, nil)
if err != nil {
return err
}
@ -105,7 +164,11 @@ func (s *local) recreateContainer(ctx context.Context, project *types.Project, s
if err != nil {
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 {
return err
}
@ -143,8 +206,8 @@ func (s *local) restartContainer(ctx context.Context, service types.ServiceConfi
return nil
}
func (s *local) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, container *moby.Container) error {
containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, container)
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, number, container)
if err != nil {
return err
}