Browse Source

Merge pull request #148 from rumpl/feat-moby-ports

Add ports to the moby backend
Chris Crone 5 years ago
parent
commit
5768ffd526
4 changed files with 134 additions and 9 deletions
  1. 1 1
      Makefile
  2. 1 1
      go.mod
  3. 76 7
      moby/backend.go
  4. 56 0
      moby/e2e/backend_test.go

+ 1 - 1
Makefile

@@ -44,7 +44,7 @@ cli: ## Compile the cli
 	--target cli
 
 e2e-local: ## Run End to end local tests
-	go test -v ./tests/e2e
+	go test -v ./tests/e2e ./moby/e2e
 
 e2e-aci: ## Run End to end ACI tests (requires azure login)
 	go test -v ./tests/aci-e2e

+ 1 - 1
go.mod

@@ -17,7 +17,7 @@ require (
 	github.com/compose-spec/compose-go v0.0.0-20200423124427-63dcf8c22cae
 	github.com/containerd/console v1.0.0
 	github.com/containerd/containerd v1.3.4 // indirect
-	github.com/docker/distribution v2.7.1+incompatible // indirect
+	github.com/docker/distribution v2.7.1+incompatible
 	github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect

+ 76 - 7
moby/backend.go

@@ -1,10 +1,14 @@
 package moby
 
 import (
+	"bufio"
 	"context"
 	"io"
+	"strconv"
 	"time"
 
+	"github.com/docker/go-connections/nat"
+
 	"github.com/docker/api/context/cloud"
 
 	"github.com/docker/docker/api/types"
@@ -72,7 +76,7 @@ func (ms *mobyService) List(ctx context.Context, all bool) ([]containers.Contain
 			// statuses. We also need to add a `Created` property on the gRPC side.
 			Status:  container.Status,
 			Command: container.Command,
-			Ports:   getPorts(container.Ports),
+			Ports:   toPorts(container.Ports),
 		})
 	}
 
@@ -80,15 +84,50 @@ func (ms *mobyService) List(ctx context.Context, all bool) ([]containers.Contain
 }
 
 func (ms *mobyService) Run(ctx context.Context, r containers.ContainerConfig) error {
-	create, err := ms.apiClient.ContainerCreate(ctx, &container.Config{
-		Image:  r.Image,
-		Labels: r.Labels,
-	}, nil, nil, r.ID)
+	exposedPorts, hostBindings, err := fromPorts(r.Ports)
 	if err != nil {
 		return err
 	}
 
-	return ms.apiClient.ContainerStart(ctx, create.ID, types.ContainerStartOptions{})
+	containerConfig := &container.Config{
+		Image:        r.Image,
+		Labels:       r.Labels,
+		ExposedPorts: exposedPorts,
+	}
+	hostConfig := &container.HostConfig{
+		PortBindings: hostBindings,
+	}
+
+	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 *mobyService) Stop(ctx context.Context, containerID string, timeout *uint32) error {
@@ -162,7 +201,7 @@ func (ms *mobyService) Delete(ctx context.Context, containerID string, force boo
 	return err
 }
 
-func getPorts(ports []types.Port) []containers.Port {
+func toPorts(ports []types.Port) []containers.Port {
 	result := []containers.Port{}
 	for _, port := range ports {
 		result = append(result, containers.Port{
@@ -175,3 +214,33 @@ func getPorts(ports []types.Port) []containers.Port {
 
 	return result
 }
+
+func fromPorts(ports []containers.Port) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
+	var (
+		exposedPorts = make(map[nat.Port]struct{}, len(ports))
+		bindings     = make(map[nat.Port][]nat.PortBinding)
+	)
+
+	for _, port := range ports {
+		p, err := nat.NewPort(port.Protocol, strconv.Itoa(int(port.ContainerPort)))
+		if err != nil {
+			return nil, nil, err
+		}
+
+		if _, exists := exposedPorts[p]; !exists {
+			exposedPorts[p] = struct{}{}
+		}
+
+		portBinding := nat.PortBinding{
+			HostIP:   port.HostIP,
+			HostPort: strconv.Itoa(int(port.HostPort)),
+		}
+		bslice, exists := bindings[p]
+		if !exists {
+			bslice = []nat.PortBinding{}
+		}
+		bindings[p] = append(bslice, portBinding)
+	}
+
+	return exposedPorts, bindings, nil
+}

+ 56 - 0
moby/e2e/backend_test.go

@@ -0,0 +1,56 @@
+package e2e
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/suite"
+
+	"github.com/docker/api/tests/framework"
+)
+
+type MobyBackendTestSuite struct {
+	framework.Suite
+}
+
+func (m *MobyBackendTestSuite) BeforeTest(suiteName string, testName string) {
+	m.NewDockerCommand("context", "create", "test-context", "moby").ExecOrDie()
+	m.NewDockerCommand("context", "use", "test-context").ExecOrDie()
+}
+
+func (m *MobyBackendTestSuite) AfterTest(suiteName string, testName string) {
+	m.NewDockerCommand("context", "rm", "test-context").ExecOrDie()
+	m.NewDockerCommand("context", "use", "default").ExecOrDie()
+}
+
+func (m *MobyBackendTestSuite) TestPs() {
+	out := m.NewDockerCommand("ps").ExecOrDie()
+	require.Equal(m.T(), "CONTAINER ID        IMAGE               COMMAND             STATUS              PORTS\n", out)
+}
+
+func (m *MobyBackendTestSuite) TestRun() {
+	_, err := m.NewDockerCommand("run", "--name", "nginx", "nginx").Exec()
+	require.Nil(m.T(), err)
+	out := m.NewDockerCommand("ps").ExecOrDie()
+	defer func() {
+		m.NewDockerCommand("rm", "-f", "nginx").ExecOrDie()
+	}()
+	lines := strings.Split(out, "\n")
+	assert.Equal(m.T(), 3, len(lines))
+}
+
+func (m *MobyBackendTestSuite) TestRunWithPorts() {
+	_, err := m.NewDockerCommand("run", "--name", "nginx", "-p", "8080:80", "nginx").Exec()
+	require.Nil(m.T(), err)
+	out := m.NewDockerCommand("ps").ExecOrDie()
+	defer func() {
+		m.NewDockerCommand("rm", "-f", "nginx").ExecOrDie()
+	}()
+	assert.Contains(m.T(), out, "8080")
+}
+
+func TestMobyBackendTestSuite(t *testing.T) {
+	suite.Run(t, new(MobyBackendTestSuite))
+}