- Implement SSH dial via stdio for remote connections - Add sources API and storage layer for managing connection sources - Add SourcePanel and SshFields web components for SSH configuration - Update app structure to support source-based connections - Update handlers and server for new sources endpoint Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
// Package dkr wraps the Docker Engine API with the operations the migration
|
|
// tool needs: reading a full container inventory, streaming data out of a
|
|
// container's mounts, and streaming image layers.
|
|
package dkr
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types/system"
|
|
"github.com/docker/docker/client"
|
|
)
|
|
|
|
// Client is a connection to one Docker daemon.
|
|
type Client struct {
|
|
api *client.Client
|
|
// Endpoint is the daemon address, shown in the UI.
|
|
Endpoint string
|
|
}
|
|
|
|
// New connects to the daemon described by the standard DOCKER_* environment
|
|
// variables, or to host when it is non-empty (e.g. unix:///var/run/docker.sock
|
|
// or tcp://10.0.0.5:2375).
|
|
func New(host string) (*Client, error) {
|
|
opts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()}
|
|
if host != "" {
|
|
opts = append(opts, client.WithHost(host))
|
|
}
|
|
api, err := client.NewClientWithOpts(opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create docker client: %w", err)
|
|
}
|
|
return &Client{api: api, Endpoint: api.DaemonHost()}, nil
|
|
}
|
|
|
|
// Dialer opens one connection to a daemon's API socket.
|
|
type Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
// NewTunnel connects to a daemon that is only reachable through dial, such as a
|
|
// remote daemon behind an SSH connection. The HTTP host is a placeholder: every
|
|
// connection comes from dial, so the address is never resolved.
|
|
//
|
|
// endpoint is what the UI displays, e.g. ssh://root@10.0.0.5.
|
|
func NewTunnel(endpoint string, dial Dialer) (*Client, error) {
|
|
// The transport is ours so that WithHost cannot leave a TCP dialer or the
|
|
// environment's HTTP proxy in place; either would send API calls somewhere
|
|
// other than through the tunnel.
|
|
tr := &http.Transport{
|
|
DisableCompression: true,
|
|
// Every connection through the tunnel costs one SSH channel, and sshd
|
|
// allows ten per connection by default (MaxSessions). Capping the pool
|
|
// keeps a parallel migration from exhausting them; extra calls wait.
|
|
MaxConnsPerHost: 8,
|
|
MaxIdleConnsPerHost: 4,
|
|
IdleConnTimeout: 5 * time.Minute,
|
|
}
|
|
api, err := client.NewClientWithOpts(
|
|
client.WithHTTPClient(&http.Client{Transport: tr}),
|
|
client.WithHost("http://docker.tunnel.invalid"),
|
|
client.WithAPIVersionNegotiation(),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create docker client: %w", err)
|
|
}
|
|
tr.Proxy = nil
|
|
tr.DialContext = dial
|
|
if endpoint == "" {
|
|
endpoint = "tunnel"
|
|
}
|
|
return &Client{api: api, Endpoint: endpoint}, nil
|
|
}
|
|
|
|
// API exposes the underlying SDK client for callers that need an operation
|
|
// this package does not wrap.
|
|
func (c *Client) API() *client.Client { return c.api }
|
|
|
|
// Close releases the daemon connection.
|
|
func (c *Client) Close() error { return c.api.Close() }
|
|
|
|
// Info returns daemon information, and doubles as a connectivity check.
|
|
func (c *Client) Info(ctx context.Context) (system.Info, error) {
|
|
return c.api.Info(ctx)
|
|
}
|
|
|
|
// Ping verifies the daemon is reachable and returns its version string.
|
|
func (c *Client) Ping(ctx context.Context) (string, error) {
|
|
v, err := c.api.ServerVersion(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return v.Version, nil
|
|
}
|