Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions e2e/framework/timeouts.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
package framework

import (
"runtime"
"time"
)

const osWindows = "windows"

func TimeoutShort() time.Duration {
if runtime.GOOS == osWindows {
return 10 * time.Minute
}
return 3 * time.Minute
}

func TimeoutModerate() time.Duration {
if runtime.GOOS == osWindows {
return 25 * time.Minute
}
return 5 * time.Minute
}

func TimeoutLong() time.Duration {
if runtime.GOOS == osWindows {
return 50 * time.Minute
}
return 10 * time.Minute
}

func TimeoutVeryLong() time.Duration {
if runtime.GOOS == osWindows {
return 100 * time.Minute
}
return 20 * time.Minute
}
2 changes: 1 addition & 1 deletion e2e/tests/up/provider_podman_rootful_lifecycle_2.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ var _ = ginkgo.Describe(
framework.ExpectNoError(err)
gomega.Expect(string(two)).To(gomega.Equal("initCmdTwo"))
},
ginkgo.SpecTimeout(framework.TimeoutShort()),
ginkgo.SpecTimeout(framework.TimeoutModerate()),
)

ginkgo.It( //nolint:dupl // mirrors rootless lifecycle secrets-file test
Expand Down
34 changes: 9 additions & 25 deletions pkg/agent/delivery/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"os"

"github.com/devsy-org/devsy/pkg/driver"
"github.com/devsy-org/devsy/pkg/inject"
Expand Down Expand Up @@ -86,19 +85,19 @@ func microsandboxDelivery(opts FactoryOptions) AgentDelivery {
return &KubernetesDelivery{Exec: opts.PodExec}
}

// dockerDelivery is only reached when the caller (NewAgentDelivery) has
// already determined, from the workspace's resolved DOCKER_HOST, that the
// daemon is local.
func dockerDelivery(opts FactoryOptions) AgentDelivery {
if isDockerLocal(opts.DockerCommand) {
log.Debugf("using local docker delivery (named volume)")
return &LocalDockerDelivery{
DockerCommand: opts.DockerCommand,
Environment: opts.DockerEnv,
HelperImage: opts.HelperImage,
}
log.Debugf("using local docker delivery (named volume)")
return &LocalDockerDelivery{
DockerCommand: opts.DockerCommand,
Environment: opts.DockerEnv,
HelperImage: opts.HelperImage,
}
log.Debugf("using remote docker delivery for non-local docker daemon")
return remoteDockerDelivery(opts)
}

// remoteDockerDelivery handles the non-local case.
func remoteDockerDelivery(opts FactoryOptions) AgentDelivery {
return &RemoteDockerDelivery{
DockerCommand: opts.DockerCommand,
Expand All @@ -118,21 +117,6 @@ func legacyShellDelivery(opts FactoryOptions, reason string) AgentDelivery {
}
}

func isDockerLocal(_ string) bool {
envHost := os.Getenv("DOCKER_HOST")
return envHost == "" || isLocalDockerHost(envHost)
}

func isLocalDockerHost(host string) bool {
if host == "" {
return true
}
hasPrefix := func(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
return hasPrefix(host, "unix://") || hasPrefix(host, "npipe://")
}

// CommandFunc adapts a driver's command function to inject.ExecFunc.
func CommandFunc(
driverCmd func(ctx context.Context, params *driver.CommandParams) error,
Expand Down
17 changes: 9 additions & 8 deletions pkg/agent/delivery/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"testing"

dockerpkg "github.com/devsy-org/devsy/pkg/docker"
"github.com/devsy-org/devsy/pkg/driver"
"github.com/devsy-org/devsy/pkg/provider"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -137,14 +138,14 @@ func TestNewAgentDelivery_KubernetesDriver_FallsBackWhenNoPodExec(t *testing.T)
assert.Equal(t, PhasePostStart, d.Phase())
}

func TestIsDockerLocal(t *testing.T) {
assert.True(t, isLocalDockerHost(""))
assert.True(t, isLocalDockerHost("unix:///var/run/docker.sock"))
assert.True(t, isLocalDockerHost("unix:///home/user/.docker/desktop/docker.sock"))
assert.True(t, isLocalDockerHost("npipe:////./pipe/docker_engine"))
assert.True(t, isLocalDockerHost("npipe:////./pipe/podman-machine-default"))
assert.False(t, isLocalDockerHost("tcp://192.168.1.100:2376"))
assert.False(t, isLocalDockerHost("ssh://user@remote-host"))
func TestIsLocalDockerHost(t *testing.T) {
assert.True(t, dockerpkg.IsLocalDockerHost(""))
assert.True(t, dockerpkg.IsLocalDockerHost("unix:///var/run/docker.sock"))
assert.True(t, dockerpkg.IsLocalDockerHost("unix:///home/user/.docker/desktop/docker.sock"))
assert.True(t, dockerpkg.IsLocalDockerHost("npipe:////./pipe/docker_engine"))
assert.True(t, dockerpkg.IsLocalDockerHost("npipe:////./pipe/podman-machine-default"))
assert.False(t, dockerpkg.IsLocalDockerHost("tcp://192.168.1.100:2376"))
assert.False(t, dockerpkg.IsLocalDockerHost("ssh://user@remote-host"))
}

func TestDeliver_PreStart(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pkg/devcontainer/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func (r *runner) newAgentDelivery() delivery.AgentDelivery {
WorkspaceID: r.id,
DockerCommand: dockerCmd,
DockerEnv: dockerEnv,
IsRemoteDocker: docker.RemoteDockerHost(dockerEnv),
HelperImage: r.workspaceConfig.Agent.Docker.HelperImage,
ContainerID: r.id,
ExecFunc: execFn,
Expand Down
46 changes: 46 additions & 0 deletions pkg/devcontainer/setup_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,60 @@
package devcontainer

import (
"reflect"
"testing"

"github.com/devsy-org/devsy/pkg/agent/delivery"
"github.com/devsy-org/devsy/pkg/devcontainer/config"
"github.com/devsy-org/devsy/pkg/docker"
provider2 "github.com/devsy-org/devsy/pkg/provider"
"github.com/devsy-org/devsy/pkg/types"
)

const testDockerHostEnvKey = "DOCKER_HOST"

func TestNewAgentDelivery_RemoteDockerHostWiring(t *testing.T) {
cases := []struct {
name string
env map[string]string
wantType any
}{
{
name: "unset DOCKER_HOST uses local delivery",
env: nil,
wantType: &delivery.LocalDockerDelivery{},
},
{
name: "unix socket DOCKER_HOST uses local delivery",
env: map[string]string{testDockerHostEnvKey: "unix:///var/run/docker.sock"},
wantType: &delivery.LocalDockerDelivery{},
},
{
name: "ssh DOCKER_HOST uses remote delivery",
env: map[string]string{testDockerHostEnvKey: "ssh://user@localhost"},
wantType: &delivery.RemoteDockerDelivery{},
},
{
name: "tcp DOCKER_HOST uses remote delivery",
env: map[string]string{testDockerHostEnvKey: "tcp://192.168.1.100:2376"},
wantType: &delivery.RemoteDockerDelivery{},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
r := newTestRunner(&mockDriver{})
r.workspaceConfig.Agent.Driver = provider2.DockerDriver
r.workspaceConfig.Agent.Docker = provider2.ProviderDockerDriverConfig{Env: tc.env}

got := r.newAgentDelivery()
if reflect.TypeOf(got) != reflect.TypeOf(tc.wantType) {
t.Errorf("newAgentDelivery() = %T, want %T", got, tc.wantType)
}
})
}
}

func TestShouldChownWorkspace(t *testing.T) {
cases := []struct {
name string
Expand Down
29 changes: 29 additions & 0 deletions pkg/docker/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,35 @@ func (r *DockerHelper) GPUSupportEnabled() (bool, error) {
return r.GetRuntime().GPUAvailable(ctx, r)
}

// IsLocalDockerHost reports whether host points at a daemon sharing the local
// filesystem. An empty value is the docker default local socket.
func IsLocalDockerHost(host string) bool {
if host == "" {
return true
}
return strings.HasPrefix(host, "unix://") || strings.HasPrefix(host, "npipe://")
}

// RemoteDockerHost reports whether env targets a daemon on a different host
// than the devsy process, by inspecting DOCKER_HOST.
func RemoteDockerHost(env []string) bool {
host, ok := envValue(env, "DOCKER_HOST")
if !ok {
return false
}
return !IsLocalDockerHost(host)
}

func envValue(env []string, name string) (string, bool) {
prefix := name + "="
for _, e := range env {
if v, ok := strings.CutPrefix(e, prefix); ok {
return v, true
}
}
return "", false
}

// GetRuntime returns the container runtime for this helper.
// If no runtime was explicitly set, it auto-detects from the docker command.
func (r *DockerHelper) GetRuntime() ContainerRuntime {
Expand Down
32 changes: 16 additions & 16 deletions pkg/driver/docker/runargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,22 +481,22 @@ func (d *dockerDriver) getRemoteUser(
}

func (d *dockerDriver) EnsurePath(path *config.Mount) *config.Mount {
// Local Windows to remote Linux over TCP requires manual path conversion.
if runtime.GOOS == "windows" {
for _, v := range d.Docker.Environment {
// Convert only when DOCKER_HOST is a direct TCP connection to a
// docker daemon running in WSL, not the docker-desktop engine.
if strings.Contains(v, "DOCKER_HOST=tcp://") {
unixPath := path.Source
unixPath = strings.Replace(unixPath, "C:", "c", 1)
unixPath = strings.ReplaceAll(unixPath, "\\", "/")
unixPath = "/mnt/" + unixPath

path.Source = unixPath

return path
}
}
if runtime.GOOS != "windows" {
return path
}
// A remote daemon (ssh://, tcp://, etc.) lives on a different host whose
// filesystem does not share Windows drive paths; translate to the WSL
// /mnt/<drive> form the remote daemon can resolve.
if !docker.RemoteDockerHost(d.Docker.Environment) {
return path
}

path.Source = windowsToWSLPath(path.Source)
return path
}

func windowsToWSLPath(winPath string) string {
unixPath := strings.Replace(winPath, "C:", "c", 1)
unixPath = strings.ReplaceAll(unixPath, "\\", "/")
return "/mnt/" + unixPath
}
86 changes: 86 additions & 0 deletions pkg/driver/docker/runargs_ensurepath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package docker

import (
"testing"

"github.com/devsy-org/devsy/pkg/devcontainer/config"
"github.com/devsy-org/devsy/pkg/docker"
)

func TestWindowsToWSLPath(t *testing.T) {
cases := []struct {
winPath string
want string
}{
{`C:\Users\me\repo`, "/mnt/c/Users/me/repo"},
{`C:\projects\security_dev`, "/mnt/c/projects/security_dev"},
{`\projects\repo`, "/mnt//projects/repo"},
}
for _, tc := range cases {
t.Run(tc.winPath, func(t *testing.T) {
if got := windowsToWSLPath(tc.winPath); got != tc.want {
t.Errorf("windowsToWSLPath(%q) = %q, want %q", tc.winPath, got, tc.want)
}
})
}
}

func TestIsLocalDockerHost(t *testing.T) {
cases := []struct {
host string
want bool
}{
{"", true},
{"unix:///var/run/docker.sock", true},
{"npipe:////./pipe/docker_engine", true},
{"tcp://localhost:2375", false},
{"ssh://user@localhost", false},
}
for _, tc := range cases {
t.Run(tc.host, func(t *testing.T) {
if got := docker.IsLocalDockerHost(tc.host); got != tc.want {
t.Errorf("IsLocalDockerHost(%q) = %v, want %v", tc.host, got, tc.want)
}
})
}
}

func TestRemoteDockerHost(t *testing.T) {
cases := []struct {
name string
env []string
want bool
}{
{name: "nil env", env: nil, want: false},
{name: "no DOCKER_HOST", env: []string{"PATH=/usr/bin"}, want: false},
{
name: "unix socket",
env: []string{"DOCKER_HOST=unix:///var/run/docker.sock"},
want: false,
},
{name: "npipe", env: []string{"DOCKER_HOST=npipe:////./pipe/docker_engine"}, want: false},
{name: "tcp", env: []string{"DOCKER_HOST=tcp://localhost:2375"}, want: true},
{name: "ssh", env: []string{"DOCKER_HOST=ssh://user@localhost"}, want: true},
{
name: "ssh with other env first",
env: []string{"PATH=/x", "DOCKER_HOST=ssh://u@localhost"},
want: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := docker.RemoteDockerHost(tc.env); got != tc.want {
t.Errorf("RemoteDockerHost(%v) = %v, want %v", tc.env, got, tc.want)
}
})
}
}

func (s *DockerDriverTestSuite) TestEnsurePath_NoopOffWindows() {
// EnsurePath only converts on Windows; guard that no-op on other OSes.
s.driver.Docker = &docker.DockerHelper{
Environment: []string{"DOCKER_HOST=ssh://user@localhost"},
}
mount := &config.Mount{Source: `C:\repo`, Target: "/workspace"}
s.Equal(`C:\repo`, s.driver.EnsurePath(mount).Source)
}
Loading
Loading