Skip to content

Commit

Permalink
docker: use a docker API client instead of a subshell (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicks authored Apr 1, 2022
1 parent b76a44e commit 653e0d7
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 118 deletions.
96 changes: 96 additions & 0 deletions internal/dctr/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package dctr

import (
"context"
"fmt"
"io"
"io/ioutil"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)

// Docker Container client.
type Client interface {
ImagePull(ctx context.Context, image string, options types.ImagePullOptions) (io.ReadCloser, error)

ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
ContainerRemove(ctx context.Context, id string, options types.ContainerRemoveOptions) error
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error)
ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error
}

// A simplified remove-container-if-necessary helper.
func RemoveIfNecessary(ctx context.Context, c Client, name string) error {
container, err := c.ContainerInspect(ctx, name)
if err != nil {
if client.IsErrNotFound(err) {
return nil
}
return err
}
if container.ContainerJSONBase == nil {
return nil
}

return c.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
Force: true,
})
}

// A simplified run-container-and-detach helper for background support containers (like socat and the registry).
func Run(ctx context.Context, c Client, name string, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) error {

ctr, err := c.ContainerInspect(ctx, name)
if err == nil && (ctr.ContainerJSONBase != nil && ctr.State.Running) {
// The service is already running!
return nil
} else if err == nil {
// The service exists, but is not running
err := c.ContainerRemove(ctx, name, types.ContainerRemoveOptions{Force: true})
if err != nil {
return fmt.Errorf("creating %s: %v", name, err)
}
} else if !client.IsErrNotFound(err) {
return fmt.Errorf("inspecting %s: %v", name, err)
}

resp, err := c.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, name)
if err != nil {
if !client.IsErrNotFound(err) {
return fmt.Errorf("creating %s: %v", name, err)
}

err := pull(ctx, c, config.Image)
if err != nil {
return fmt.Errorf("pulling image %s: %v", config.Image, err)
}

resp, err = c.ContainerCreate(ctx, config, hostConfig, networkingConfig, nil, name)
if err != nil {
return fmt.Errorf("creating %s: %v", name, err)
}
}

id := resp.ID
err = c.ContainerStart(ctx, id, types.ContainerStartOptions{})
if err != nil {
return fmt.Errorf("starting %s: %v", name, err)
}
return nil
}

func pull(ctx context.Context, c Client, image string) error {
resp, err := c.ImagePull(ctx, image, types.ImagePullOptions{})
if err != nil {
return fmt.Errorf("pulling image %s: %v", image, err)
}
defer resp.Close()

_, _ = io.Copy(ioutil.Discard, resp)
return nil
}
46 changes: 20 additions & 26 deletions internal/socat/socat.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@ import (
"strings"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/shirou/gopsutil/v3/process"
"github.com/tilt-dev/ctlptl/internal/dctr"
"github.com/tilt-dev/ctlptl/pkg/docker"
)

const serviceName = "ctlptl-portforward-service"

type ContainerClient interface {
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
ContainerRemove(ctx context.Context, id string, options types.ContainerRemoveOptions) error
}

type Controller struct {
client ContainerClient
client dctr.Client
}

func NewController(client ContainerClient) *Controller {
func NewController(client dctr.Client) *Controller {
return &Controller{client: client}
}

Expand Down Expand Up @@ -57,24 +54,21 @@ func (c *Controller) ConnectRemoteDockerPort(ctx context.Context, port int) erro
// Docker. This server accepts connections and routes them to localhost ports
// on the same machine.
func (c *Controller) StartRemotePortforwarder(ctx context.Context) error {
container, err := c.client.ContainerInspect(ctx, serviceName)
if err == nil && (container.ContainerJSONBase != nil && container.State.Running) {
// The service is already running!
return nil
} else if err == nil {
// The service exists, but is not running
err := c.client.ContainerRemove(ctx, serviceName, types.ContainerRemoveOptions{Force: true})
if err != nil {
return fmt.Errorf("creating remote portforwarder: %v", err)
}
} else if !client.IsErrNotFound(err) {
return fmt.Errorf("inspecting remote portforwarder: %v", err)
}

cmd := exec.Command("docker", "run", "-d", "-it",
"--name", serviceName, "--net=host", "--restart=always",
"--entrypoint", "/bin/sh", "alpine/socat", "-c", "while true; do sleep 1000; done")
return cmd.Run()
return dctr.Run(
ctx,
c.client,
serviceName,
&container.Config{
Hostname: serviceName,
Image: "alpine/socat",
Entrypoint: []string{"/bin/sh"},
Cmd: []string{"-c", "while true; do sleep 1000; done"},
},
&container.HostConfig{
NetworkMode: "host",
RestartPolicy: container.RestartPolicy{Name: "always"},
},
&network.NetworkingConfig{})
}

// Returns the socat process listening on a port, plus its commandline.
Expand Down
18 changes: 18 additions & 0 deletions pkg/cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import (
"bytes"
"context"
"fmt"
"io"
"os"
"testing"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tilt-dev/clusterid"
Expand Down Expand Up @@ -493,6 +496,21 @@ func (d *fakeDockerClient) ContainerRemove(ctx context.Context, id string, optio
return nil
}

func (d *fakeDockerClient) ImagePull(ctx context.Context, image string, options types.ImagePullOptions) (io.ReadCloser, error) {
return nil, nil
}

func (d *fakeDockerClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
return nil, nil
}

func (d *fakeDockerClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) {
return container.ContainerCreateCreatedBody{}, nil
}
func (d *fakeDockerClient) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
return nil
}

func (d *fakeDockerClient) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/cluster/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/tilt-dev/ctlptl/internal/dctr"
"github.com/tilt-dev/ctlptl/pkg/docker"
)

type dockerClient interface {
dctr.Client
IsLocalDockerEngine() bool
ServerVersion(ctx context.Context) (types.Version, error)
Info(ctx context.Context) (types.Info, error)
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
ContainerRemove(ctx context.Context, id string, options types.ContainerRemoveOptions) error
NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error
NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error
}
Expand Down
Loading

0 comments on commit 653e0d7

Please sign in to comment.