-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
socat: add a utility to forward ports to a remote docker server (#94)
- Loading branch information
Showing
3 changed files
with
142 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Manage socat network routers for remote docker instances. | ||
package socat | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
"os/exec" | ||
"time" | ||
|
||
"github.com/docker/docker/api/types" | ||
"github.com/docker/docker/client" | ||
) | ||
|
||
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 | ||
} | ||
|
||
func NewController(client ContainerClient) *Controller { | ||
return &Controller{client: client} | ||
} | ||
|
||
func DefaultController(ctx context.Context) (*Controller, error) { | ||
client, err := client.NewClientWithOpts(client.FromEnv) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
client.NegotiateAPIVersion(ctx) | ||
return NewController(client), nil | ||
} | ||
|
||
// Connect a port on the local machine to a port on a remote docker machine. | ||
func (c *Controller) ConnectRemoteDockerPort(ctx context.Context, port int) error { | ||
err := c.StartRemotePortforwarder(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
return c.StartLocalPortforwarder(ctx, port) | ||
} | ||
|
||
// Create a port-forwarding server on the same machine that's running | ||
// 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.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() | ||
} | ||
|
||
// Create a port-forwarding server on the local machine, forwarding connections | ||
// to the same port on the remote Docker server. | ||
func (c *Controller) StartLocalPortforwarder(ctx context.Context, port int) error { | ||
cmd := exec.Command("socat", fmt.Sprintf("TCP-LISTEN:%d,reuseaddr,fork", port), | ||
fmt.Sprintf("EXEC:'docker exec -i %s socat STDIO TCP:localhost:%d'", serviceName, port)) | ||
err := cmd.Start() | ||
if err != nil { | ||
return fmt.Errorf("creating local portforwarder: %v", err) | ||
} | ||
|
||
for i := 0; i < 100; i++ { | ||
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) | ||
if err == nil { | ||
_ = conn.Close() | ||
return nil | ||
} | ||
time.Sleep(100 * time.Millisecond) | ||
} | ||
return fmt.Errorf("timed out waiting for local portforwarder") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"strconv" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/tilt-dev/ctlptl/internal/socat" | ||
) | ||
|
||
func NewSocatCommand() *cobra.Command { | ||
var cmd = &cobra.Command{ | ||
Use: "socat", | ||
Short: "Use socat to connect components. Experimental.", | ||
} | ||
|
||
cmd.AddCommand(&cobra.Command{ | ||
Use: "connect-remote-docker", | ||
Short: "Connects a local port to a remote port on a machine running Docker", | ||
Example: " ctlptl socat connect-remote-docker [port]\n", | ||
Run: connectRemoteDocker, | ||
Args: cobra.ExactArgs(1), | ||
}) | ||
|
||
return cmd | ||
} | ||
|
||
func connectRemoteDocker(cmd *cobra.Command, args []string) { | ||
port, err := strconv.Atoi(args[0]) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
ctx := context.Background() | ||
c, err := socat.DefaultController(ctx) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
err = c.ConnectRemoteDockerPort(ctx, port) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err) | ||
os.Exit(1) | ||
} | ||
} |