Browse Source

composeService to rely on apiClient

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 years ago
parent
commit
646aca82d9
4 changed files with 94 additions and 67 deletions
  1. 3 1
      local/backend.go
  2. 17 4
      local/build.go
  3. 53 41
      local/compose.go
  4. 21 21
      local/convergence.go

+ 3 - 1
local/backend.go

@@ -35,6 +35,7 @@ import (
 type local struct {
 	*containerService
 	*volumeService
+	*composeService
 }
 
 func init() {
@@ -50,6 +51,7 @@ func service(ctx context.Context) (backend.Service, error) {
 	return &local{
 		containerService: &containerService{apiClient},
 		volumeService:    &volumeService{apiClient},
+		composeService:   &composeService{apiClient},
 	}, nil
 }
 
@@ -58,7 +60,7 @@ func (s *local) ContainerService() containers.Service {
 }
 
 func (s *local) ComposeService() compose.Service {
-	return s
+	return s.composeService
 }
 
 func (s *local) SecretsService() secrets.Service {

+ 17 - 4
local/build.go

@@ -25,6 +25,8 @@ import (
 	"path"
 	"strings"
 
+	"github.com/docker/docker/errdefs"
+
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/buildx/build"
 	"github.com/docker/buildx/driver"
@@ -32,7 +34,7 @@ import (
 	"github.com/docker/buildx/util/progress"
 )
 
-func (s *local) ensureImagesExists(ctx context.Context, project *types.Project) error {
+func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project) error {
 	opts := map[string]build.Options{}
 	for _, service := range project.Services {
 		if service.Image == "" && service.Build == nil {
@@ -71,9 +73,20 @@ func (s *local) ensureImagesExists(ctx context.Context, project *types.Project)
 	return s.build(ctx, project, opts)
 }
 
-func (s *local) build(ctx context.Context, project *types.Project, opts map[string]build.Options) error {
+func (s *composeService) needPull(ctx context.Context, service types.ServiceConfig) (bool, error) {
+	_, _, err := s.apiClient.ImageInspectWithRaw(ctx, service.Image)
+	if err != nil {
+		if errdefs.IsNotFound(err) {
+			return true, nil
+		}
+		return false, err
+	}
+	return false, nil
+}
+
+func (s *composeService) build(ctx context.Context, project *types.Project, opts map[string]build.Options) error {
 	const drivername = "default"
-	d, err := driver.GetDriver(ctx, drivername, nil, s.containerService.apiClient, nil, nil, "", nil, project.WorkingDir)
+	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, nil, nil, "", nil, project.WorkingDir)
 	if err != nil {
 		return err
 	}
@@ -89,7 +102,7 @@ func (s *local) build(ctx context.Context, project *types.Project, opts map[stri
 	return err
 }
 
-func (s *local) buildImage(ctx context.Context, service types.ServiceConfig, contextPath string) build.Options {
+func (s *composeService) buildImage(ctx context.Context, service types.ServiceConfig, contextPath string) build.Options {
 	var tags []string
 	if service.Image != "" {
 		tags = append(tags, service.Image)

+ 53 - 41
local/compose.go

@@ -27,7 +27,6 @@ import (
 	"sort"
 	"strconv"
 	"strings"
-	"sync"
 
 	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
@@ -36,19 +35,25 @@ import (
 	"github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/strslice"
+	mobyvolume "github.com/docker/docker/api/types/volume"
+	"github.com/docker/docker/client"
 	"github.com/docker/docker/errdefs"
+	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/docker/go-connections/nat"
 	"github.com/pkg/errors"
 	"github.com/sanathkr/go-yaml"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose-cli/api/compose"
-	"github.com/docker/compose-cli/api/containers"
 	"github.com/docker/compose-cli/formatter"
 	"github.com/docker/compose-cli/progress"
 )
 
-func (s *local) Up(ctx context.Context, project *types.Project, detach bool) error {
+type composeService struct {
+	apiClient *client.Client
+}
+
+func (s *composeService) Up(ctx context.Context, project *types.Project, detach bool) error {
 	for k, network := range project.Networks {
 		if !network.External.External && network.Name != "" {
 			network.Name = fmt.Sprintf("%s_%s", project.Name, k)
@@ -95,19 +100,8 @@ func getContainerName(c moby.Container) string {
 	return c.Names[0][1:]
 }
 
-func (s *local) needPull(ctx context.Context, service types.ServiceConfig) (bool, error) {
-	_, _, err := s.containerService.apiClient.ImageInspectWithRaw(ctx, service.Image)
-	if err != nil {
-		if errdefs.IsNotFound(err) {
-			return true, nil
-		}
-		return false, err
-	}
-	return false, nil
-}
-
-func (s *local) Down(ctx context.Context, projectName string) error {
-	list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+func (s *composeService) Down(ctx context.Context, projectName string) error {
+	list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(
 			projectFilter(projectName),
 		),
@@ -126,7 +120,7 @@ func (s *local) Down(ctx context.Context, projectName string) error {
 				Text:   "Stopping",
 				Status: progress.Working,
 			})
-			err := s.containerService.Stop(ctx, container.ID, nil)
+			err := s.apiClient.ContainerStop(ctx, container.ID, nil)
 			if err != nil {
 				return err
 			}
@@ -135,7 +129,7 @@ func (s *local) Down(ctx context.Context, projectName string) error {
 				Text:   "Removing",
 				Status: progress.Working,
 			})
-			err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
+			err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
 			if err != nil {
 				return err
 			}
@@ -150,8 +144,8 @@ func (s *local) Down(ctx context.Context, projectName string) error {
 	return eg.Wait()
 }
 
-func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error {
-	list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+func (s *composeService) Logs(ctx context.Context, projectName string, w io.Writer) error {
+	list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(
 			projectFilter(projectName),
 		),
@@ -159,26 +153,41 @@ func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error
 	if err != nil {
 		return err
 	}
-	var wg sync.WaitGroup
 	consumer := formatter.NewLogConsumer(w)
+	eg, ctx := errgroup.WithContext(ctx)
 	for _, c := range list {
 		service := c.Labels[serviceLabel]
-		containerID := c.ID
-		go func() {
-			_ = s.containerService.Logs(ctx, containerID, containers.LogsRequest{
-				Follow: true,
-				Writer: consumer.GetWriter(service, containerID),
+		container, err := s.apiClient.ContainerInspect(ctx, c.ID)
+		if err != nil {
+			return err
+		}
+
+		eg.Go(func() error {
+			r, err := s.apiClient.ContainerLogs(ctx, container.ID, moby.ContainerLogsOptions{
+				ShowStdout: true,
+				ShowStderr: true,
+				Follow:     true,
 			})
-			wg.Done()
-		}()
-		wg.Add(1)
+			defer r.Close() // nolint errcheck
+
+			if err != nil {
+				return err
+			}
+			w := consumer.GetWriter(service, container.ID)
+			if container.Config.Tty {
+				_, err = io.Copy(w, r)
+			} else {
+				_, err = stdcopy.StdCopy(w, w, r)
+			}
+			return err
+		})
 	}
-	wg.Wait()
+	eg.Wait()
 	return nil
 }
 
-func (s *local) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
-	list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
+	list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(
 			projectFilter(projectName),
 		),
@@ -233,8 +242,8 @@ func groupContainerByLabel(containers []moby.Container, labelName string) (map[s
 	return containersByLabel, keys, nil
 }
 
-func (s *local) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
-	list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+func (s *composeService) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
+	list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(hasProjectLabelFilter()),
 	})
 	if err != nil {
@@ -291,7 +300,7 @@ func combinedStatus(statuses []string) string {
 	return result
 }
 
-func (s *local) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
+func (s *composeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
 	switch format {
 	case "json":
 		return json.MarshalIndent(project, "", "  ")
@@ -535,8 +544,8 @@ func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetwo
 	return map[string]*types.ServiceNetworkConfig{"default": nil}
 }
 
-func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
-	_, err := s.containerService.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
+func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
+	_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
 	if err != nil {
 		if errdefs.IsNotFound(err) {
 			createOpts := moby.NetworkCreate{
@@ -568,7 +577,7 @@ func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error
 				Status:     progress.Working,
 				StatusText: "Create",
 			})
-			if _, err := s.containerService.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
+			if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
 				return errors.Wrapf(err, "failed to create network %s", n.Name)
 			}
 			w.Event(progress.Event{
@@ -583,9 +592,9 @@ func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error
 	return nil
 }
 
-func (s *local) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
+func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
 	// TODO could identify volume by label vs name
-	_, err := s.volumeService.Inspect(ctx, volume.Name)
+	_, err := s.apiClient.VolumeInspect(ctx, volume.Name)
 	if err != nil {
 		if errdefs.IsNotFound(err) {
 			w := progress.ContextWriter(ctx)
@@ -595,7 +604,10 @@ func (s *local) ensureVolume(ctx context.Context, volume types.VolumeConfig) err
 				StatusText: "Create",
 			})
 			// TODO we miss support for driver_opts and labels
-			_, err := s.volumeService.Create(ctx, volume.Name, nil)
+			_, err := s.apiClient.VolumeCreate(ctx, mobyvolume.VolumeCreateBody{
+				Labels: nil,
+				Name:   volume.Name,
+			})
 			w.Event(progress.Event{
 				ID:         fmt.Sprintf("Volume %q", volume.Name),
 				Status:     progress.Done,

+ 21 - 21
local/convergence.go

@@ -30,7 +30,6 @@ import (
 	"github.com/docker/docker/api/types/network"
 	"golang.org/x/sync/errgroup"
 
-	"github.com/docker/compose-cli/api/containers"
 	"github.com/docker/compose-cli/progress"
 )
 
@@ -39,13 +38,13 @@ const (
 	forceRecreate = "force_recreate"
 )
 
-func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
+func (s *composeService) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 	err := s.waitDependencies(ctx, project, service)
 	if err != nil {
 		return err
 	}
 
-	actual, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+	actual, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(
 			filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, project.Name)),
 			filters.Arg("label", fmt.Sprintf("%s=%s", serviceLabel, service.Name)),
@@ -77,11 +76,11 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
 		for i := scale; i < len(actual); i++ {
 			container := actual[i]
 			eg.Go(func() error {
-				err := s.containerService.Stop(ctx, container.ID, nil)
+				err := s.apiClient.ContainerStop(ctx, container.ID, nil)
 				if err != nil {
 					return err
 				}
-				return s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
+				return s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
 			})
 		}
 		actual = actual[:scale]
@@ -114,7 +113,7 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
 	return eg.Wait()
 }
 
-func (s *local) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
+func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 	eg, _ := errgroup.WithContext(ctx)
 	for dep, config := range service.DependsOn {
 		switch config.Condition {
@@ -163,7 +162,7 @@ func getScale(config types.ServiceConfig) int {
 	return 1
 }
 
-func (s *local) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int) error {
+func (s *composeService) 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),
@@ -182,20 +181,20 @@ func (s *local) createContainer(ctx context.Context, project *types.Project, ser
 	return nil
 }
 
-func (s *local) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container) error {
+func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container) error {
 	w := progress.ContextWriter(ctx)
 	w.Event(progress.Event{
 		ID:         fmt.Sprintf("Service %q", service.Name),
 		Status:     progress.Working,
 		StatusText: "Recreate",
 	})
-	err := s.containerService.Stop(ctx, container.ID, nil)
+	err := s.apiClient.ContainerStop(ctx, container.ID, nil)
 	if err != nil {
 		return err
 	}
 	name := getContainerName(container)
 	tmpName := fmt.Sprintf("%s_%s", container.ID[:12], name)
-	err = s.containerService.apiClient.ContainerRename(ctx, container.ID, tmpName)
+	err = s.apiClient.ContainerRename(ctx, container.ID, tmpName)
 	if err != nil {
 		return err
 	}
@@ -207,7 +206,7 @@ func (s *local) recreateContainer(ctx context.Context, project *types.Project, s
 	if err != nil {
 		return err
 	}
-	err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
+	err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
 	if err != nil {
 		return err
 	}
@@ -233,14 +232,14 @@ func setDependentLifecycle(project *types.Project, service string, strategy stri
 	}
 }
 
-func (s *local) restartContainer(ctx context.Context, service types.ServiceConfig, container moby.Container) error {
+func (s *composeService) restartContainer(ctx context.Context, service types.ServiceConfig, container moby.Container) error {
 	w := progress.ContextWriter(ctx)
 	w.Event(progress.Event{
 		ID:         fmt.Sprintf("Service %q", service.Name),
 		Status:     progress.Working,
 		StatusText: "Restart",
 	})
-	err := s.containerService.Start(ctx, container.ID)
+	err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 	if err != nil {
 		return err
 	}
@@ -252,15 +251,16 @@ 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, number int, container *moby.Container) error {
+func (s *composeService) 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
 	}
-	id, err := s.containerService.create(ctx, containerConfig, hostConfig, networkingConfig, name)
+	created, err := s.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, name)
 	if err != nil {
 		return err
 	}
+	id := created.ID
 	for net := range service.Networks {
 		name := fmt.Sprintf("%s_%s", project.Name, net)
 		err = s.connectContainerToNetwork(ctx, id, service.Name, name)
@@ -268,15 +268,15 @@ func (s *local) runContainer(ctx context.Context, project *types.Project, servic
 			return err
 		}
 	}
-	err = s.containerService.apiClient.ContainerStart(ctx, id, moby.ContainerStartOptions{})
+	err = s.apiClient.ContainerStart(ctx, id, moby.ContainerStartOptions{})
 	if err != nil {
 		return err
 	}
 	return nil
 }
 
-func (s *local) connectContainerToNetwork(ctx context.Context, id string, service string, n string) error {
-	err := s.containerService.apiClient.NetworkConnect(ctx, n, id, &network.EndpointSettings{
+func (s *composeService) connectContainerToNetwork(ctx context.Context, id string, service string, n string) error {
+	err := s.apiClient.NetworkConnect(ctx, n, id, &network.EndpointSettings{
 		Aliases: []string{service},
 	})
 	if err != nil {
@@ -285,8 +285,8 @@ func (s *local) connectContainerToNetwork(ctx context.Context, id string, servic
 	return nil
 }
 
-func (s *local) isServiceHealthy(ctx context.Context, project *types.Project, service string) (bool, error) {
-	containers, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string) (bool, error) {
+	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
 		Filters: filters.NewArgs(
 			filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, project.Name)),
 			filters.Arg("label", fmt.Sprintf("%s=%s", serviceLabel, service)),
@@ -297,7 +297,7 @@ func (s *local) isServiceHealthy(ctx context.Context, project *types.Project, se
 	}
 
 	for _, c := range containers {
-		container, err := s.containerService.apiClient.ContainerInspect(ctx, c.ID)
+		container, err := s.apiClient.ContainerInspect(ctx, c.ID)
 		if err != nil {
 			return false, err
 		}