Browse Source

backend.local: Refactor container service

Signed-off-by: Chris Crone <[email protected]>
Chris Crone 5 years ago
parent
commit
637dd263c9
2 changed files with 254 additions and 223 deletions
  1. 4 223
      local/backend.go
  2. 250 0
      local/containers.go

+ 4 - 223
local/backend.go

@@ -19,18 +19,9 @@
 package local
 
 import (
-	"bufio"
 	"context"
-	"io"
-	"strings"
-	"time"
 
-	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/client"
-	"github.com/docker/docker/pkg/stdcopy"
-	"github.com/docker/docker/pkg/stringid"
-	"github.com/pkg/errors"
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
@@ -39,11 +30,10 @@ import (
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
 	"github.com/docker/compose-cli/context/cloud"
-	"github.com/docker/compose-cli/errdefs"
 )
 
 type local struct {
-	apiClient *client.Client
+	*containerService
 }
 
 func init() {
@@ -57,12 +47,12 @@ func service(ctx context.Context) (backend.Service, error) {
 	}
 
 	return &local{
-		apiClient,
+		containerService: &containerService{apiClient},
 	}, nil
 }
 
-func (ms *local) ContainerService() containers.Service {
-	return ms
+func (cs *containerService) ContainerService() containers.Service {
+	return cs
 }
 
 func (ms *local) ComposeService() compose.Service {
@@ -80,212 +70,3 @@ func (ms *local) VolumeService() volumes.Service {
 func (ms *local) ResourceService() resources.Service {
 	return nil
 }
-
-func (ms *local) Inspect(ctx context.Context, id string) (containers.Container, error) {
-	c, err := ms.apiClient.ContainerInspect(ctx, id)
-	if err != nil {
-		return containers.Container{}, err
-	}
-
-	status := ""
-	if c.State != nil {
-		status = c.State.Status
-	}
-
-	command := ""
-	if c.Config != nil &&
-		c.Config.Cmd != nil {
-		command = strings.Join(c.Config.Cmd, " ")
-	}
-
-	rc := toRuntimeConfig(&c)
-	hc := toHostConfig(&c)
-
-	return containers.Container{
-		ID:         stringid.TruncateID(c.ID),
-		Status:     status,
-		Image:      c.Image,
-		Command:    command,
-		Platform:   c.Platform,
-		Config:     rc,
-		HostConfig: hc,
-	}, nil
-}
-
-func (ms *local) List(ctx context.Context, all bool) ([]containers.Container, error) {
-	css, err := ms.apiClient.ContainerList(ctx, types.ContainerListOptions{
-		All: all,
-	})
-
-	if err != nil {
-		return []containers.Container{}, err
-	}
-
-	var result []containers.Container
-	for _, container := range css {
-		result = append(result, containers.Container{
-			ID:    stringid.TruncateID(container.ID),
-			Image: container.Image,
-			// TODO: `Status` is a human readable string ("Up 24 minutes"),
-			// we need to return the `State` instead but first we need to
-			// define an enum on the proto side with all the possible container
-			// statuses. We also need to add a `Created` property on the gRPC side.
-			Status:  container.Status,
-			Command: container.Command,
-			Ports:   toPorts(container.Ports),
-		})
-	}
-
-	return result, nil
-}
-
-func (ms *local) Run(ctx context.Context, r containers.ContainerConfig) error {
-	exposedPorts, hostBindings, err := fromPorts(r.Ports)
-	if err != nil {
-		return err
-	}
-
-	containerConfig := &container.Config{
-		Image:        r.Image,
-		Labels:       r.Labels,
-		Env:          r.Environment,
-		ExposedPorts: exposedPorts,
-	}
-	hostConfig := &container.HostConfig{
-		PortBindings:  hostBindings,
-		AutoRemove:    r.AutoRemove,
-		RestartPolicy: toRestartPolicy(r.RestartPolicyCondition),
-		Resources: container.Resources{
-			NanoCPUs: int64(r.CPULimit * 1e9),
-			Memory:   int64(r.MemLimit),
-		},
-	}
-
-	created, err := ms.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID)
-
-	if err != nil {
-		if client.IsErrNotFound(err) {
-			io, err := ms.apiClient.ImagePull(ctx, r.Image, types.ImagePullOptions{})
-			if err != nil {
-				return err
-			}
-			scanner := bufio.NewScanner(io)
-
-			// Read the whole body, otherwise the pulling stops
-			for scanner.Scan() {
-			}
-
-			if err = scanner.Err(); err != nil {
-				return err
-			}
-			if err = io.Close(); err != nil {
-				return err
-			}
-			created, err = ms.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID)
-			if err != nil {
-				return err
-			}
-		} else {
-			return err
-		}
-	}
-
-	return ms.apiClient.ContainerStart(ctx, created.ID, types.ContainerStartOptions{})
-}
-
-func (ms *local) Start(ctx context.Context, containerID string) error {
-	return ms.apiClient.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
-}
-
-func (ms *local) Stop(ctx context.Context, containerID string, timeout *uint32) error {
-	var t *time.Duration
-	if timeout != nil {
-		timeoutValue := time.Duration(*timeout) * time.Second
-		t = &timeoutValue
-	}
-	return ms.apiClient.ContainerStop(ctx, containerID, t)
-}
-
-func (ms *local) Kill(ctx context.Context, containerID string, signal string) error {
-	return ms.apiClient.ContainerKill(ctx, containerID, signal)
-}
-
-func (ms *local) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
-	cec, err := ms.apiClient.ContainerExecCreate(ctx, name, types.ExecConfig{
-		Cmd:          []string{request.Command},
-		Tty:          true,
-		AttachStdin:  true,
-		AttachStdout: true,
-		AttachStderr: true,
-	})
-	if err != nil {
-		return err
-	}
-	resp, err := ms.apiClient.ContainerExecAttach(ctx, cec.ID, types.ExecStartCheck{
-		Tty: true,
-	})
-	if err != nil {
-		return err
-	}
-	defer resp.Close()
-
-	readChannel := make(chan error, 10)
-	writeChannel := make(chan error, 10)
-
-	go func() {
-		_, err := io.Copy(request.Stdout, resp.Reader)
-		readChannel <- err
-	}()
-
-	go func() {
-		_, err := io.Copy(resp.Conn, request.Stdin)
-		writeChannel <- err
-	}()
-
-	for {
-		select {
-		case err := <-readChannel:
-			return err
-		case err := <-writeChannel:
-			return err
-		}
-	}
-}
-
-func (ms *local) Logs(ctx context.Context, containerName string, request containers.LogsRequest) error {
-	c, err := ms.apiClient.ContainerInspect(ctx, containerName)
-	if err != nil {
-		return err
-	}
-
-	r, err := ms.apiClient.ContainerLogs(ctx, containerName, types.ContainerLogsOptions{
-		ShowStdout: true,
-		ShowStderr: true,
-		Follow:     request.Follow,
-	})
-
-	if err != nil {
-		return err
-	}
-
-	// nolint errcheck
-	defer r.Close()
-
-	if c.Config.Tty {
-		_, err = io.Copy(request.Writer, r)
-	} else {
-		_, err = stdcopy.StdCopy(request.Writer, request.Writer, r)
-	}
-
-	return err
-}
-
-func (ms *local) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error {
-	err := ms.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
-		Force: request.Force,
-	})
-	if client.IsErrNotFound(err) {
-		return errors.Wrapf(errdefs.ErrNotFound, "container %q", containerID)
-	}
-	return err
-}

+ 250 - 0
local/containers.go

@@ -0,0 +1,250 @@
+// +build local
+
+/*
+   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 local
+
+import (
+	"bufio"
+	"context"
+	"io"
+	"strings"
+	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/client"
+	"github.com/docker/docker/pkg/stdcopy"
+	"github.com/docker/docker/pkg/stringid"
+	"github.com/pkg/errors"
+
+	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/errdefs"
+)
+
+type containerService struct {
+	apiClient *client.Client
+}
+
+func (cs *containerService) Inspect(ctx context.Context, id string) (containers.Container, error) {
+	c, err := cs.apiClient.ContainerInspect(ctx, id)
+	if err != nil {
+		return containers.Container{}, err
+	}
+
+	status := ""
+	if c.State != nil {
+		status = c.State.Status
+	}
+
+	command := ""
+	if c.Config != nil &&
+		c.Config.Cmd != nil {
+		command = strings.Join(c.Config.Cmd, " ")
+	}
+
+	rc := toRuntimeConfig(&c)
+	hc := toHostConfig(&c)
+
+	return containers.Container{
+		ID:         stringid.TruncateID(c.ID),
+		Status:     status,
+		Image:      c.Image,
+		Command:    command,
+		Platform:   c.Platform,
+		Config:     rc,
+		HostConfig: hc,
+	}, nil
+}
+
+func (cs *containerService) List(ctx context.Context, all bool) ([]containers.Container, error) {
+	css, err := cs.apiClient.ContainerList(ctx, types.ContainerListOptions{
+		All: all,
+	})
+
+	if err != nil {
+		return []containers.Container{}, err
+	}
+
+	var result []containers.Container
+	for _, container := range css {
+		result = append(result, containers.Container{
+			ID:    stringid.TruncateID(container.ID),
+			Image: container.Image,
+			// TODO: `Status` is a human readable string ("Up 24 minutes"),
+			// we need to return the `State` instead but first we need to
+			// define an enum on the proto side with all the possible container
+			// statuses. We also need to add a `Created` property on the gRPC side.
+			Status:  container.Status,
+			Command: container.Command,
+			Ports:   toPorts(container.Ports),
+		})
+	}
+
+	return result, nil
+}
+
+func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfig) error {
+	exposedPorts, hostBindings, err := fromPorts(r.Ports)
+	if err != nil {
+		return err
+	}
+
+	containerConfig := &container.Config{
+		Image:        r.Image,
+		Labels:       r.Labels,
+		Env:          r.Environment,
+		ExposedPorts: exposedPorts,
+	}
+	hostConfig := &container.HostConfig{
+		PortBindings:  hostBindings,
+		AutoRemove:    r.AutoRemove,
+		RestartPolicy: toRestartPolicy(r.RestartPolicyCondition),
+		Resources: container.Resources{
+			NanoCPUs: int64(r.CPULimit * 1e9),
+			Memory:   int64(r.MemLimit),
+		},
+	}
+
+	created, err := cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID)
+
+	if err != nil {
+		if client.IsErrNotFound(err) {
+			io, err := cs.apiClient.ImagePull(ctx, r.Image, types.ImagePullOptions{})
+			if err != nil {
+				return err
+			}
+			scanner := bufio.NewScanner(io)
+
+			// Read the whole body, otherwise the pulling stops
+			for scanner.Scan() {
+			}
+
+			if err = scanner.Err(); err != nil {
+				return err
+			}
+			if err = io.Close(); err != nil {
+				return err
+			}
+			created, err = cs.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, r.ID)
+			if err != nil {
+				return err
+			}
+		} else {
+			return err
+		}
+	}
+
+	return cs.apiClient.ContainerStart(ctx, created.ID, types.ContainerStartOptions{})
+}
+
+func (cs *containerService) Start(ctx context.Context, containerID string) error {
+	return cs.apiClient.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
+}
+
+func (cs *containerService) Stop(ctx context.Context, containerID string, timeout *uint32) error {
+	var t *time.Duration
+	if timeout != nil {
+		timeoutValue := time.Duration(*timeout) * time.Second
+		t = &timeoutValue
+	}
+	return cs.apiClient.ContainerStop(ctx, containerID, t)
+}
+
+func (cs *containerService) Kill(ctx context.Context, containerID string, signal string) error {
+	return cs.apiClient.ContainerKill(ctx, containerID, signal)
+}
+
+func (cs *containerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
+	cec, err := cs.apiClient.ContainerExecCreate(ctx, name, types.ExecConfig{
+		Cmd:          []string{request.Command},
+		Tty:          true,
+		AttachStdin:  true,
+		AttachStdout: true,
+		AttachStderr: true,
+	})
+	if err != nil {
+		return err
+	}
+	resp, err := cs.apiClient.ContainerExecAttach(ctx, cec.ID, types.ExecStartCheck{
+		Tty: true,
+	})
+	if err != nil {
+		return err
+	}
+	defer resp.Close()
+
+	readChannel := make(chan error, 10)
+	writeChannel := make(chan error, 10)
+
+	go func() {
+		_, err := io.Copy(request.Stdout, resp.Reader)
+		readChannel <- err
+	}()
+
+	go func() {
+		_, err := io.Copy(resp.Conn, request.Stdin)
+		writeChannel <- err
+	}()
+
+	for {
+		select {
+		case err := <-readChannel:
+			return err
+		case err := <-writeChannel:
+			return err
+		}
+	}
+}
+
+func (cs *containerService) Logs(ctx context.Context, containerName string, request containers.LogsRequest) error {
+	c, err := cs.apiClient.ContainerInspect(ctx, containerName)
+	if err != nil {
+		return err
+	}
+
+	r, err := cs.apiClient.ContainerLogs(ctx, containerName, types.ContainerLogsOptions{
+		ShowStdout: true,
+		ShowStderr: true,
+		Follow:     request.Follow,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	// nolint errcheck
+	defer r.Close()
+
+	if c.Config.Tty {
+		_, err = io.Copy(request.Writer, r)
+	} else {
+		_, err = stdcopy.StdCopy(request.Writer, request.Writer, r)
+	}
+
+	return err
+}
+
+func (cs *containerService) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error {
+	err := cs.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
+		Force: request.Force,
+	})
+	if client.IsErrNotFound(err) {
+		return errors.Wrapf(errdefs.ErrNotFound, "container %q", containerID)
+	}
+	return err
+}