Skip to content

Commit 3dd4972

Browse files
authored
refactor: use workspace proxy for setup server (#1790)
1 parent 27e3251 commit 3dd4972

File tree

14 files changed

+434
-737
lines changed

14 files changed

+434
-737
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ require (
2929
github.com/gorilla/websocket v1.5.3
3030
github.com/joho/godotenv v1.5.1
3131
github.com/julienschmidt/httprouter v1.3.0
32-
github.com/loft-sh/agentapi/v4 v4.3.0-devpod.alpha.11
32+
github.com/loft-sh/agentapi/v4 v4.3.0-devpod.alpha.16
3333
github.com/loft-sh/analytics-client v0.0.0-20240219162240-2f4c64b2494e
34-
github.com/loft-sh/api/v4 v4.3.0-devpod.alpha.11
34+
github.com/loft-sh/api/v4 v4.3.0-devpod.alpha.16
3535
github.com/loft-sh/apiserver v0.0.0-20250206205835-422f1d472459
3636
github.com/loft-sh/log v0.0.0-20240219160058-26d83ffb46ac
3737
github.com/loft-sh/programming-language-detection v0.0.5

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,12 +422,12 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
422422
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
423423
github.com/loft-sh/admin-apis v0.0.0-20250221182517-7499d86167d2 h1:om1MqUdW84ZQc0GMGGgFfPI6xpTbrF+6DwKVq+76R44=
424424
github.com/loft-sh/admin-apis v0.0.0-20250221182517-7499d86167d2/go.mod h1:WHCqWfljfD1hkwk41hLeqBhW2yeLvWipB1sH6vfnR7U=
425-
github.com/loft-sh/agentapi/v4 v4.3.0-devpod.alpha.11 h1:1tcoDVN5ZGdhKMvW2ySqsyemWD/5DaNg3Z9VbDS73uA=
426-
github.com/loft-sh/agentapi/v4 v4.3.0-devpod.alpha.11/go.mod h1:UteiUdc6QeGt2uXIo5K5qlReRa366GbPY5QdDuPyFCs=
425+
github.com/loft-sh/agentapi/v4 v4.3.0-devpod.alpha.16 h1:a7iemRlezfhsmSWJYmhPpeMNIkRZFHhrJL538nLCqlM=
426+
github.com/loft-sh/agentapi/v4 v4.3.0-devpod.alpha.16/go.mod h1:UteiUdc6QeGt2uXIo5K5qlReRa366GbPY5QdDuPyFCs=
427427
github.com/loft-sh/analytics-client v0.0.0-20240219162240-2f4c64b2494e h1:JcPnMaoczikvpasi8OJ47dCkWZjfgFubWa4V2SZo7h0=
428428
github.com/loft-sh/analytics-client v0.0.0-20240219162240-2f4c64b2494e/go.mod h1:FFWcGASyM2QlWTDTCG/WBVM/XYr8btqYt335TFNRCFg=
429-
github.com/loft-sh/api/v4 v4.3.0-devpod.alpha.11 h1:AbYZX2KjmCzGKe3HyVDY89oSVNk72EwImzVfuCIRdzE=
430-
github.com/loft-sh/api/v4 v4.3.0-devpod.alpha.11/go.mod h1:IDuvg5bFg9zzG1NB7OOpmggIZ23jib9ZQ8x8wB8qYf8=
429+
github.com/loft-sh/api/v4 v4.3.0-devpod.alpha.16 h1:tvVofFbGDND2ifiAQeA9gHCUhFR1GHBntlltSDs9fTk=
430+
github.com/loft-sh/api/v4 v4.3.0-devpod.alpha.16/go.mod h1:QvDqPCWhP8VG8GH8OnbJ3ps8SmdDiF6f2qHDI2mvqBI=
431431
github.com/loft-sh/apiserver v0.0.0-20250206205835-422f1d472459 h1:6SrgBtT1S9ANsQMoO/O0Mq+hs9EbC5te5kPqOBfg5UI=
432432
github.com/loft-sh/apiserver v0.0.0-20250206205835-422f1d472459/go.mod h1:rung3jsKjaVAtykQN0vWmFHhx2A/umpRyAae8BJVSeE=
433433
github.com/loft-sh/log v0.0.0-20240219160058-26d83ffb46ac h1:Gz/7Lb7WgdgIv+KJz87ORA1zvQW52tUqKPGyunlp4dQ=

pkg/client/clientimplementation/daemonclient/client.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,12 @@ func (c *client) SSHClients(ctx context.Context, user string) (toolClient *ssh.C
173173
return nil, nil, fmt.Errorf("resolve workspace hostname: %w", err)
174174
}
175175

176-
toolClient, err = ts.WaitForSSHClient(ctx, c.tsClient, wAddr.Host(), wAddr.Port(), "root", c.log)
176+
address := fmt.Sprintf("%s:%d", wAddr.Host(), wAddr.Port())
177+
toolClient, err = ts.WaitForSSHClient(ctx, c.tsClient.Dial, "tcp", address, "root", time.Second*10, c.log)
177178
if err != nil {
178179
return nil, nil, fmt.Errorf("create SSH tool client: %w", err)
179180
}
180-
userClient, err = ts.WaitForSSHClient(ctx, c.tsClient, wAddr.Host(), wAddr.Port(), user, c.log)
181+
userClient, err = ts.WaitForSSHClient(ctx, c.tsClient.Dial, "tcp", address, user, time.Second*10, c.log)
181182
if err != nil {
182183
return nil, nil, fmt.Errorf("create SSH user client: %w", err)
183184
}

pkg/devcontainer/setup.go

Lines changed: 100 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"net"
89
"os"
910
"path/filepath"
1011
"runtime"
@@ -20,6 +21,9 @@ import (
2021
"github.com/loft-sh/devpod/pkg/driver"
2122
"github.com/loft-sh/devpod/pkg/ide"
2223
provider2 "github.com/loft-sh/devpod/pkg/provider"
24+
devssh "github.com/loft-sh/devpod/pkg/ssh"
25+
"github.com/loft-sh/devpod/pkg/ssh/server"
26+
"github.com/loft-sh/devpod/pkg/ts"
2327
"github.com/loft-sh/log"
2428
"github.com/pkg/errors"
2529
"github.com/sirupsen/logrus"
@@ -96,15 +100,6 @@ func (r *runner) setupContainer(
96100
// check if docker driver
97101
_, isDockerDriver := r.Driver.(driver.DockerDriver)
98102

99-
// ssh tunnel
100-
sshTunnelCmd := fmt.Sprintf("'%s' helper ssh-server --stdio", agent.ContainerDevPodHelperLocation)
101-
if ide.ReusesAuthSock(r.WorkspaceConfig.Workspace.IDE.Name) {
102-
sshTunnelCmd += fmt.Sprintf(" --reuse-ssh-auth-sock=%s", r.WorkspaceConfig.CLIOptions.SSHAuthSockID)
103-
}
104-
if r.Log.GetLevel() == logrus.DebugLevel {
105-
sshTunnelCmd += " --debug"
106-
}
107-
108103
// setup container
109104
r.Log.Infof("Setup container...")
110105

@@ -132,10 +127,46 @@ func (r *runner) setupContainer(
132127
setupCommand += " --debug"
133128
}
134129

130+
// run setup server
131+
runSetupServer := func(ctx context.Context, stdin io.WriteCloser, stdout io.Reader) (*config.Result, error) {
132+
return tunnelserver.RunSetupServer(
133+
ctx,
134+
stdout,
135+
stdin,
136+
r.WorkspaceConfig.Agent.InjectGitCredentials != "false",
137+
r.WorkspaceConfig.Agent.InjectDockerCredentials != "false",
138+
config.GetMounts(result),
139+
r.Log,
140+
tunnelserver.WithPlatformOptions(&r.WorkspaceConfig.CLIOptions.Platform),
141+
)
142+
}
143+
144+
// check if we should use the platform workspace socket
145+
shouldUsePlatformWorkspaceSocket := r.WorkspaceConfig.CLIOptions.Platform.Enabled && r.WorkspaceConfig.CLIOptions.Platform.WorkspaceSocket != ""
146+
if shouldUsePlatformWorkspaceSocket {
147+
_, err := os.Stat(r.WorkspaceConfig.CLIOptions.Platform.WorkspaceSocket)
148+
if err != nil {
149+
shouldUsePlatformWorkspaceSocket = false
150+
}
151+
}
152+
153+
// if we can use the platform workspace socket we connect directly to it
154+
if shouldUsePlatformWorkspaceSocket {
155+
return r.runPlatformSetupServer(ctx, setupCommand, runSetupServer)
156+
}
157+
158+
// ssh tunnel
159+
sshTunnelCmd := fmt.Sprintf("'%s' helper ssh-server --stdio", agent.ContainerDevPodHelperLocation)
160+
if ide.ReusesAuthSock(r.WorkspaceConfig.Workspace.IDE.Name) {
161+
sshTunnelCmd += fmt.Sprintf(" --reuse-ssh-auth-sock=%s", r.WorkspaceConfig.CLIOptions.SSHAuthSockID)
162+
}
163+
if r.Log.GetLevel() == logrus.DebugLevel {
164+
sshTunnelCmd += " --debug"
165+
}
166+
135167
agentInjectFunc := func(cancelCtx context.Context, sshCmd string, sshTunnelStdinReader, sshTunnelStdoutWriter *os.File, writer io.WriteCloser) error {
136168
return r.Driver.CommandDevContainer(cancelCtx, r.ID, "root", sshCmd, sshTunnelStdinReader, sshTunnelStdoutWriter, writer)
137169
}
138-
139170
return sshtunnel.ExecuteCommand(
140171
ctx,
141172
nil,
@@ -144,21 +175,68 @@ func (r *runner) setupContainer(
144175
sshTunnelCmd,
145176
setupCommand,
146177
r.Log,
147-
func(ctx context.Context, stdin io.WriteCloser, stdout io.Reader) (*config.Result, error) {
148-
return tunnelserver.RunSetupServer(
149-
ctx,
150-
stdout,
151-
stdin,
152-
r.WorkspaceConfig.Agent.InjectGitCredentials != "false",
153-
r.WorkspaceConfig.Agent.InjectDockerCredentials != "false",
154-
config.GetMounts(result),
155-
r.Log,
156-
tunnelserver.WithPlatformOptions(&r.WorkspaceConfig.CLIOptions.Platform),
157-
)
158-
},
178+
runSetupServer,
159179
)
160180
}
161181

182+
func (r *runner) runPlatformSetupServer(ctx context.Context, setupCommand string, tunnelServerFunc sshtunnel.TunnelServerFunc) (*config.Result, error) {
183+
r.Log.Infof("Connecting to workspace...")
184+
185+
// create a dialer that connects to the platform workspace socket
186+
dialer := func(ctx context.Context, network, address string) (net.Conn, error) {
187+
dial := &net.Dialer{}
188+
return dial.DialContext(ctx, "unix", r.WorkspaceConfig.CLIOptions.Platform.WorkspaceSocket)
189+
}
190+
191+
// start machine on stdio
192+
ctx, cancel := context.WithCancel(ctx)
193+
defer cancel()
194+
195+
// create a new direct ssh client
196+
toolClient, err := ts.WaitForSSHClient(ctx, dialer, "tcp", fmt.Sprintf("%s:%d", r.WorkspaceConfig.CLIOptions.Platform.WorkspaceHost, server.DefaultUserPort), "root", time.Second*30, r.Log)
197+
if err != nil {
198+
return nil, fmt.Errorf("create SSH tool client: %w", err)
199+
}
200+
defer toolClient.Close()
201+
202+
// create the pipes
203+
stdoutReader, stdoutWriter, err := os.Pipe()
204+
if err != nil {
205+
return nil, err
206+
}
207+
stdinReader, stdinWriter, err := os.Pipe()
208+
if err != nil {
209+
return nil, err
210+
}
211+
defer stdoutWriter.Close()
212+
defer stdinWriter.Close()
213+
214+
// create the error channel & execute remote command
215+
errChan := make(chan error, 1)
216+
go func() {
217+
defer cancel()
218+
219+
writer := r.Log.Writer(logrus.InfoLevel, false)
220+
defer writer.Close()
221+
222+
err = devssh.Run(ctx, toolClient, setupCommand, stdinReader, stdoutWriter, writer, nil)
223+
if err != nil {
224+
errChan <- errors.Wrap(err, "run agent command")
225+
} else {
226+
errChan <- nil
227+
}
228+
}()
229+
230+
// start tunnel server locally
231+
result, err := tunnelServerFunc(ctx, stdinWriter, stdoutReader)
232+
if err != nil {
233+
return nil, fmt.Errorf("start tunnel server: %w", err)
234+
}
235+
236+
// wait until command finished
237+
return result, <-errChan
238+
}
239+
162240
func getRelativeDevContainerJson(origin, localWorkspaceFolder string) string {
163241
relativePath := strings.TrimPrefix(filepath.ToSlash(origin), filepath.ToSlash(localWorkspaceFolder))
164242
return strings.TrimPrefix(relativePath, "/")

pkg/ts/ssh.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@ package ts
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"time"
78

89
"github.com/loft-sh/log"
910
"golang.org/x/crypto/ssh"
10-
tsClient "tailscale.com/client/tailscale"
1111
)
1212

13-
func WaitForSSHClient(ctx context.Context, lc *tsClient.LocalClient, host string, port int, user string, log log.Logger) (*ssh.Client, error) {
14-
deadline := time.Now().Add(10 * time.Second)
13+
type Dialer func(ctx context.Context, network, address string) (net.Conn, error)
14+
15+
func WaitForSSHClient(ctx context.Context, dialer Dialer, network, address string, user string, timeout time.Duration, log log.Logger) (*ssh.Client, error) {
16+
deadline := time.Now().Add(timeout)
1517

1618
var (
1719
c *ssh.Client
1820
err error
1921
)
20-
log.Debugf("Attempting to establish SSH connection with %s as user %s", host, user)
22+
log.Debugf("Attempting to establish SSH connection with %s as user %s", address, user)
2123
for time.Now().Before(deadline) {
22-
c, err = newSSHClient(ctx, lc, host, port, user)
24+
c, err = newSSHClient(ctx, dialer, network, address, user)
2325
if err == nil {
2426
return c, nil
2527
}
@@ -35,10 +37,10 @@ func WaitForSSHClient(ctx context.Context, lc *tsClient.LocalClient, host string
3537
return c, err
3638
}
3739

38-
func newSSHClient(ctx context.Context, lc *tsClient.LocalClient, host string, port int, user string) (*ssh.Client, error) {
39-
conn, err := lc.DialTCP(ctx, host, uint16(port))
40+
func newSSHClient(ctx context.Context, dialer Dialer, network, address string, user string) (*ssh.Client, error) {
41+
conn, err := dialer(ctx, network, address)
4042
if err != nil {
41-
return nil, fmt.Errorf("dial %s: %w", host, err)
43+
return nil, fmt.Errorf("dial %s: %w", address, err)
4244
}
4345

4446
clientConfig := &ssh.ClientConfig{
@@ -47,8 +49,7 @@ func newSSHClient(ctx context.Context, lc *tsClient.LocalClient, host string, po
4749
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
4850
}
4951

50-
serverAddress := fmt.Sprintf("%s:%d", host, port)
51-
sshConn, channels, requests, err := ssh.NewClientConn(conn, serverAddress, clientConfig)
52+
sshConn, channels, requests, err := ssh.NewClientConn(conn, address, clientConfig)
5253
if err != nil {
5354
return nil, fmt.Errorf("establish SSH connection: %w", err)
5455
}

0 commit comments

Comments
 (0)