mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-22 23:21:03 +00:00
Enhance Podman support with improved socket detection and error handling across platforms
Signed-off-by: DeiAsPie <93835541+DeiAsPie@users.noreply.github.com>
This commit is contained in:
parent
cb517d9e09
commit
81e4e44309
5 changed files with 408 additions and 91 deletions
|
|
@ -23,7 +23,13 @@ var (
|
|||
const socketValidationTimeout = 3 * time.Second
|
||||
|
||||
var (
|
||||
validateSocketFunc = validateSocket
|
||||
validateSocketFunc = validateSocket
|
||||
getHostFromContextFunc = getHostFromContext
|
||||
detectPlatformCandidatesFunc = detectPlatformCandidates
|
||||
|
||||
// For testing getHostFromContext
|
||||
cliconfigLoadFunc = cliconfig.Load
|
||||
ctxstoreNewFunc = ctxstore.New
|
||||
)
|
||||
|
||||
// Runtime type detection
|
||||
|
|
@ -67,7 +73,7 @@ func ResetDockerHostCache() {
|
|||
dockerHostMu.Lock()
|
||||
defer dockerHostMu.Unlock()
|
||||
cachedDockerHost = ""
|
||||
cachedRuntime = ""
|
||||
cachedRuntime = RuntimeUnknown
|
||||
}
|
||||
|
||||
func detectDockerHostInternal(log *logrus.Entry) (string, ContainerRuntime, error) {
|
||||
|
|
@ -78,8 +84,8 @@ func detectDockerHostInternal(log *logrus.Entry) (string, ContainerRuntime, erro
|
|||
// Handle plain paths without schema
|
||||
if !strings.Contains(dockerHost, "://") {
|
||||
if _, err := os.Stat(dockerHost); err == nil {
|
||||
log.Debugf("DOCKER_HOST is a plain path, assuming unix://")
|
||||
dockerHost = "unix://" + dockerHost
|
||||
log.Debugf("DOCKER_HOST is a plain path, assuming %s", DockerSocketSchema)
|
||||
dockerHost = DockerSocketSchema + dockerHost
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,14 +93,14 @@ func detectDockerHostInternal(log *logrus.Entry) (string, ContainerRuntime, erro
|
|||
ctx, cancel := context.WithTimeout(context.Background(), socketValidationTimeout)
|
||||
defer cancel()
|
||||
if err := validateSocketFunc(ctx, dockerHost, true); err != nil {
|
||||
log.Warnf("DOCKER_HOST=%s is set but not accessible: %v", dockerHost, err)
|
||||
return "", RuntimeUnknown, fmt.Errorf("DOCKER_HOST=%s is set but not accessible: %w", dockerHost, err)
|
||||
}
|
||||
}
|
||||
return dockerHost, RuntimeUnknown, nil
|
||||
return dockerHost, RuntimeDocker, nil
|
||||
}
|
||||
|
||||
// Priority 2: Docker Context
|
||||
contextHost, err := getHostFromContext()
|
||||
contextHost, err := getHostFromContextFunc()
|
||||
if err != nil {
|
||||
// If DOCKER_CONTEXT was explicitly set, we should fail
|
||||
if os.Getenv("DOCKER_CONTEXT") != "" {
|
||||
|
|
@ -103,25 +109,33 @@ func detectDockerHostInternal(log *logrus.Entry) (string, ContainerRuntime, erro
|
|||
log.Debugf("Failed to get host from default context: %v", err)
|
||||
} else if contextHost != "" {
|
||||
log.Debugf("Using host from Docker context: %s", contextHost)
|
||||
isValid := true
|
||||
if !strings.HasPrefix(contextHost, "ssh://") {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), socketValidationTimeout)
|
||||
defer cancel()
|
||||
if err := validateSocketFunc(ctx, contextHost, false); err != nil {
|
||||
if os.Getenv("DOCKER_CONTEXT") != "" {
|
||||
return "", RuntimeUnknown, fmt.Errorf("DOCKER_CONTEXT host %s is not accessible: %w", contextHost, err)
|
||||
}
|
||||
log.Warnf("Context host %s is not accessible: %v", contextHost, err)
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
return contextHost, RuntimeUnknown, nil
|
||||
|
||||
if isValid {
|
||||
return contextHost, RuntimeDocker, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 3: Platform-specific candidates
|
||||
return detectPlatformCandidates(log)
|
||||
return detectPlatformCandidatesFunc(log)
|
||||
}
|
||||
|
||||
// getHostFromContext retrieves the host from the current Docker context
|
||||
func getHostFromContext() (string, error) {
|
||||
currentContext := os.Getenv("DOCKER_CONTEXT")
|
||||
if currentContext == "" {
|
||||
cf, err := cliconfig.Load(cliconfig.Dir())
|
||||
cf, err := cliconfigLoadFunc(cliconfig.Dir())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -137,7 +151,7 @@ func getHostFromContext() (string, error) {
|
|||
ctxstore.EndpointTypeGetter(ddocker.DockerEndpoint, func() interface{} { return &ddocker.EndpointMeta{} }),
|
||||
)
|
||||
|
||||
st := ctxstore.New(cliconfig.ContextStoreDir(), storeConfig)
|
||||
st := ctxstoreNewFunc(cliconfig.ContextStoreDir(), storeConfig)
|
||||
md, err := st.GetMetadata(currentContext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
|||
|
|
@ -8,38 +8,52 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSocketCandidates(t *testing.T) {
|
||||
// Save env vars
|
||||
oldXdg := os.Getenv("XDG_RUNTIME_DIR")
|
||||
oldHome := os.Getenv("HOME")
|
||||
// Save and restore
|
||||
oldGetuid := getuidFunc
|
||||
oldGetenv := getenvFunc
|
||||
oldUserHomeDir := userHomeDirFunc
|
||||
defer func() {
|
||||
os.Setenv("XDG_RUNTIME_DIR", oldXdg)
|
||||
os.Setenv("HOME", oldHome)
|
||||
getuidFunc = oldGetuid
|
||||
getenvFunc = oldGetenv
|
||||
userHomeDirFunc = oldUserHomeDir
|
||||
}()
|
||||
|
||||
os.Setenv("XDG_RUNTIME_DIR", "/tmp/runtime")
|
||||
os.Setenv("HOME", "/home/user")
|
||||
getuidFunc = func() int { return 1000 }
|
||||
getenvFunc = func(key string) string {
|
||||
if key == "XDG_RUNTIME_DIR" {
|
||||
return "/run/user/1000"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
userHomeDirFunc = func() (string, error) { return "/home/user", nil }
|
||||
|
||||
candidates := getSocketCandidates()
|
||||
|
||||
// Check some expected candidates
|
||||
foundDocker := false
|
||||
foundPodman := false
|
||||
for _, c := range candidates {
|
||||
if c.Path == "unix:///var/run/docker.sock" {
|
||||
foundDocker = true
|
||||
}
|
||||
if c.Path == "unix:///tmp/runtime/podman/podman.sock" {
|
||||
foundPodman = true
|
||||
}
|
||||
expectedPaths := []string{
|
||||
"unix:///var/run/docker.sock",
|
||||
"unix:///run/user/1000/docker.sock",
|
||||
"unix:///home/user/.docker/run/docker.sock",
|
||||
"unix:///run/user/1000/podman/podman.sock",
|
||||
"unix:///home/user/.colima/default/docker.sock",
|
||||
"unix:///home/user/.orbstack/run/docker.sock",
|
||||
"unix:///home/user/.lima/default/sock/docker.sock",
|
||||
"unix:///home/user/.rd/docker.sock",
|
||||
}
|
||||
|
||||
assert.True(t, foundDocker, "Standard Docker socket should be in candidates")
|
||||
assert.True(t, foundPodman, "Rootless Podman socket should be in candidates")
|
||||
var paths []string
|
||||
for _, c := range candidates {
|
||||
paths = append(paths, c.Path)
|
||||
}
|
||||
|
||||
for _, expected := range expectedPaths {
|
||||
assert.Contains(t, paths, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_DOCKER_HOST_Priority(t *testing.T) {
|
||||
|
|
@ -50,6 +64,13 @@ func TestDetectDockerHost_DOCKER_HOST_Priority(t *testing.T) {
|
|||
expectedHost := "unix:///tmp/custom.sock"
|
||||
os.Setenv("DOCKER_HOST", expectedHost)
|
||||
|
||||
// Mock validateSocketFunc to succeed
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() { validateSocketFunc = oldValidate }()
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset cache for test
|
||||
ResetDockerHostCache()
|
||||
|
||||
|
|
@ -58,6 +79,7 @@ func TestDetectDockerHost_DOCKER_HOST_Priority(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedHost, host)
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_Caching(t *testing.T) {
|
||||
// Save env var
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
|
|
@ -65,6 +87,13 @@ func TestDetectDockerHost_Caching(t *testing.T) {
|
|||
|
||||
os.Setenv("DOCKER_HOST", "unix:///tmp/first.sock")
|
||||
|
||||
// Mock validateSocketFunc to succeed
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() { validateSocketFunc = oldValidate }()
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset cache for test
|
||||
ResetDockerHostCache()
|
||||
|
||||
|
|
@ -78,6 +107,89 @@ func TestDetectDockerHost_Caching(t *testing.T) {
|
|||
assert.Equal(t, host1, host2)
|
||||
assert.Equal(t, "unix:///tmp/first.sock", host2)
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_DOCKER_HOST_Invalid(t *testing.T) {
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
os.Setenv("DOCKER_HOST", "unix:///tmp/invalid.sock")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
ResetDockerHostCache()
|
||||
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() { validateSocketFunc = oldValidate }()
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
return errors.New("invalid")
|
||||
}
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
_, _, err := DetectDockerHost(log)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "DOCKER_HOST")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_Context_Success(t *testing.T) {
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
os.Setenv("DOCKER_HOST", "")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
oldGetHost := getHostFromContextFunc
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() {
|
||||
getHostFromContextFunc = oldGetHost
|
||||
validateSocketFunc = oldValidate
|
||||
}()
|
||||
|
||||
getHostFromContextFunc = func() (string, error) {
|
||||
return "unix:///tmp/context.sock", nil
|
||||
}
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
if host == "unix:///tmp/context.sock" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid")
|
||||
}
|
||||
|
||||
ResetDockerHostCache()
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
host, _, err := DetectDockerHost(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unix:///tmp/context.sock", host)
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_Context_Invalid_Fallback(t *testing.T) {
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
os.Setenv("DOCKER_HOST", "")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
oldGetHost := getHostFromContextFunc
|
||||
oldValidate := validateSocketFunc
|
||||
oldDetectPlatform := detectPlatformCandidatesFunc
|
||||
defer func() {
|
||||
getHostFromContextFunc = oldGetHost
|
||||
validateSocketFunc = oldValidate
|
||||
detectPlatformCandidatesFunc = oldDetectPlatform
|
||||
}()
|
||||
|
||||
getHostFromContextFunc = func() (string, error) {
|
||||
return "unix:///tmp/invalid-context.sock", nil
|
||||
}
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
return errors.New("invalid")
|
||||
}
|
||||
detectPlatformCandidatesFunc = func(log *logrus.Entry) (string, ContainerRuntime, error) {
|
||||
return "unix:///tmp/fallback.sock", RuntimeDocker, nil
|
||||
}
|
||||
|
||||
ResetDockerHostCache()
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
host, _, err := DetectDockerHost(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unix:///tmp/fallback.sock", host)
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_Context_Invalid(t *testing.T) {
|
||||
// Save env vars
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
|
|
@ -99,15 +211,123 @@ func TestDetectDockerHost_Context_Invalid(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to use DOCKER_CONTEXT")
|
||||
}
|
||||
|
||||
func TestGetHostFromContext(t *testing.T) {
|
||||
// This test is tricky because it depends on the Docker CLI config.
|
||||
// We'll skip it if we can't easily mock the config directory.
|
||||
// But we can at least test the "default" case.
|
||||
host, err := getHostFromContext()
|
||||
if err == nil {
|
||||
// If it succeeded, it should be empty or a valid host
|
||||
assert.True(t, host == "" || host != "")
|
||||
func TestDetectDockerHost_Context_Error_NoEnv(t *testing.T) {
|
||||
// Save and restore
|
||||
oldGetHost := getHostFromContextFunc
|
||||
oldDetectPlatform := detectPlatformCandidatesFunc
|
||||
defer func() {
|
||||
getHostFromContextFunc = oldGetHost
|
||||
detectPlatformCandidatesFunc = oldDetectPlatform
|
||||
}()
|
||||
|
||||
// Mock getHostFromContext to return error
|
||||
getHostFromContextFunc = func() (string, error) {
|
||||
return "", errors.New("context error")
|
||||
}
|
||||
|
||||
// Mock detectPlatformCandidates to return success so we can see it falls through
|
||||
detectPlatformCandidatesFunc = func(log *logrus.Entry) (string, ContainerRuntime, error) {
|
||||
return "unix:///tmp/fallback.sock", RuntimeDocker, nil
|
||||
}
|
||||
|
||||
// Clear DOCKER_HOST
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
os.Setenv("DOCKER_HOST", "")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
ResetDockerHostCache()
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
host, runtime, err := DetectDockerHost(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unix:///tmp/fallback.sock", host)
|
||||
assert.Equal(t, RuntimeDocker, runtime)
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_SSH(t *testing.T) {
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
os.Setenv("DOCKER_HOST", "ssh://user@host")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
ResetDockerHostCache()
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
host, _, err := DetectDockerHost(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ssh://user@host", host)
|
||||
}
|
||||
|
||||
func TestDetectDockerHost_PlainPath(t *testing.T) {
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
// Create a temporary file to act as a socket
|
||||
tmpFile, err := os.CreateTemp("", "lazydocker-test-*.sock")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
tmpFile.Close()
|
||||
|
||||
os.Setenv("DOCKER_HOST", tmpFile.Name())
|
||||
|
||||
ResetDockerHostCache()
|
||||
|
||||
// Mock validateSocketFunc to succeed
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() { validateSocketFunc = oldValidate }()
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
host, _, err := DetectDockerHost(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unix://"+tmpFile.Name(), host)
|
||||
}
|
||||
|
||||
func TestValidateSocket_UseEnv(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
oldDockerHost := os.Getenv("DOCKER_HOST")
|
||||
os.Setenv("DOCKER_HOST", "unix:///tmp/test.sock")
|
||||
defer os.Setenv("DOCKER_HOST", oldDockerHost)
|
||||
|
||||
// This will fail ping but cover the useEnv branch
|
||||
err := validateSocket(ctx, "unix:///tmp/test.sock", true)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "ping failed")
|
||||
}
|
||||
|
||||
func TestGetHostFromContext_Implementation(t *testing.T) {
|
||||
// Save and restore
|
||||
oldLoad := cliconfigLoadFunc
|
||||
defer func() { cliconfigLoadFunc = oldLoad }()
|
||||
|
||||
t.Run("DOCKER_CONTEXT set", func(t *testing.T) {
|
||||
oldCtx := os.Getenv("DOCKER_CONTEXT")
|
||||
os.Setenv("DOCKER_CONTEXT", "default")
|
||||
defer os.Setenv("DOCKER_CONTEXT", oldCtx)
|
||||
|
||||
host, err := getHostFromContext()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, host)
|
||||
})
|
||||
|
||||
t.Run("Config load success but empty context", func(t *testing.T) {
|
||||
cliconfigLoadFunc = func(dir string) (*configfile.ConfigFile, error) {
|
||||
return &configfile.ConfigFile{CurrentContext: ""}, nil
|
||||
}
|
||||
host, err := getHostFromContext()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, host)
|
||||
})
|
||||
|
||||
t.Run("Config load error", func(t *testing.T) {
|
||||
cliconfigLoadFunc = func(dir string) (*configfile.ConfigFile, error) {
|
||||
return nil, errors.New("load error")
|
||||
}
|
||||
_, err := getHostFromContext()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "load error")
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateSocket_Failures(t *testing.T) {
|
||||
|
|
@ -123,58 +343,95 @@ func TestValidateSocket_Failures(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDetectPlatformCandidates_Unix(t *testing.T) {
|
||||
// Mock validateSocketFunc to always fail
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() { validateSocketFunc = oldValidate }()
|
||||
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
return errors.New("mock failure")
|
||||
}
|
||||
|
||||
// Mock environment to ensure no candidates are found
|
||||
oldXdg := os.Getenv("XDG_RUNTIME_DIR")
|
||||
oldHome := os.Getenv("HOME")
|
||||
defer func() {
|
||||
os.Setenv("XDG_RUNTIME_DIR", oldXdg)
|
||||
os.Setenv("HOME", oldHome)
|
||||
}()
|
||||
|
||||
os.Setenv("XDG_RUNTIME_DIR", "/tmp/nonexistent-xdg")
|
||||
os.Setenv("HOME", "/tmp/nonexistent-home")
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
_, _, err := detectPlatformCandidates(log)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), ErrNoDockerSocket.Error())
|
||||
}
|
||||
|
||||
func TestDetectPlatformCandidates_Unix_Success(t *testing.T) {
|
||||
// Mock validateSocketFunc to succeed for a specific path
|
||||
oldValidate := validateSocketFunc
|
||||
// Save and restore
|
||||
oldStat := statFunc
|
||||
oldValidate := validateSocketFunc
|
||||
defer func() {
|
||||
validateSocketFunc = oldValidate
|
||||
statFunc = oldStat
|
||||
validateSocketFunc = oldValidate
|
||||
}()
|
||||
|
||||
expectedPath := "unix:///var/run/docker.sock"
|
||||
statFunc = func(name string) (os.FileInfo, error) {
|
||||
if name == "/var/run/docker.sock" {
|
||||
return nil, nil // Mock success
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
log := logrus.New().WithField("test", "test")
|
||||
|
||||
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
|
||||
if host == expectedPath {
|
||||
return nil
|
||||
t.Run("No sockets exist", func(t *testing.T) {
|
||||
statFunc = func(name string) (os.FileInfo, error) {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return errors.New("mock failure")
|
||||
}
|
||||
_, _, err := detectPlatformCandidates(log)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "ensure Docker or Podman is running")
|
||||
})
|
||||
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
host, runtime, err := detectPlatformCandidates(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedPath, host)
|
||||
assert.Equal(t, RuntimeDocker, runtime)
|
||||
t.Run("Docker socket exists and is valid", func(t *testing.T) {
|
||||
statFunc = func(name string) (os.FileInfo, error) {
|
||||
if name == "/var/run/docker.sock" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
|
||||
if host == "unix:///var/run/docker.sock" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid")
|
||||
}
|
||||
path, runtime, err := detectPlatformCandidates(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unix:///var/run/docker.sock", path)
|
||||
assert.Equal(t, RuntimeDocker, runtime)
|
||||
})
|
||||
|
||||
t.Run("Docker socket exists but permission denied", func(t *testing.T) {
|
||||
statFunc = func(name string) (os.FileInfo, error) {
|
||||
if name == "/var/run/docker.sock" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
|
||||
return errors.New("permission denied")
|
||||
}
|
||||
_, _, err := detectPlatformCandidates(log)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "permission denied")
|
||||
})
|
||||
|
||||
t.Run("Docker socket exists but other error", func(t *testing.T) {
|
||||
statFunc = func(name string) (os.FileInfo, error) {
|
||||
if name == "/var/run/docker.sock" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
|
||||
return errors.New("other error")
|
||||
}
|
||||
_, _, err := detectPlatformCandidates(log)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "other error")
|
||||
})
|
||||
|
||||
t.Run("Podman socket exists and is valid", func(t *testing.T) {
|
||||
// Mock getuid to return 1000
|
||||
oldGetuid := getuidFunc
|
||||
getuidFunc = func() int { return 1000 }
|
||||
defer func() { getuidFunc = oldGetuid }()
|
||||
|
||||
statFunc = func(name string) (os.FileInfo, error) {
|
||||
if name == "/run/user/1000/podman/podman.sock" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
|
||||
if host == "unix:///run/user/1000/podman/podman.sock" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid")
|
||||
}
|
||||
path, runtime, err := detectPlatformCandidates(log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unix:///run/user/1000/podman/podman.sock", path)
|
||||
assert.Equal(t, RuntimePodman, runtime)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
statFunc = os.Stat
|
||||
statFunc = os.Stat
|
||||
getuidFunc = os.Getuid
|
||||
getenvFunc = os.Getenv
|
||||
userHomeDirFunc = os.UserHomeDir
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -48,9 +51,9 @@ func getSocketCandidates() []SocketCandidate {
|
|||
// 1. Standard Docker daemon socket
|
||||
addCandidate(DockerSocketPath, RuntimeDocker)
|
||||
|
||||
xdgRuntime := os.Getenv("XDG_RUNTIME_DIR")
|
||||
home, _ := os.UserHomeDir()
|
||||
uid := os.Getuid()
|
||||
xdgRuntime := getenvFunc("XDG_RUNTIME_DIR")
|
||||
home, _ := userHomeDirFunc()
|
||||
uid := getuidFunc()
|
||||
|
||||
// 2. Rootless Docker: $XDG_RUNTIME_DIR/docker.sock
|
||||
if xdgRuntime != "" {
|
||||
|
|
@ -139,7 +142,7 @@ func detectPlatformCandidates(log *logrus.Entry) (string, ContainerRuntime, erro
|
|||
log.Debugf("Socket %s exists but validation failed: %v", candidate.Path, err)
|
||||
errStr := strings.ToLower(err.Error())
|
||||
if strings.Contains(errStr, "permission denied") || strings.Contains(errStr, "eacces") {
|
||||
lastErr = fmt.Errorf("%s: permission denied (are you in the docker group?)", candidate.Path)
|
||||
lastErr = fmt.Errorf("%s: permission denied (are you in the %v group?)", candidate.Path, candidate.Runtime)
|
||||
} else {
|
||||
lastErr = fmt.Errorf("%s: %v", candidate.Path, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,17 @@ const (
|
|||
defaultDockerHost = DockerSocketSchema + DockerSocketPath
|
||||
)
|
||||
|
||||
func getPodmanPipes() []string {
|
||||
func getPodmanPipes(log *logrus.Entry) []string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Debugf("Failed to get user home directory: %v", err)
|
||||
return []string{"npipe:////./pipe/podman-machine-default"}
|
||||
}
|
||||
|
||||
configDir := filepath.Join(home, ".config", "containers", "podman", "machine", "wsl")
|
||||
files, err := os.ReadDir(configDir)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to read Podman machine config directory %s: %v", configDir, err)
|
||||
return []string{"npipe:////./pipe/podman-machine-default"}
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +41,7 @@ func getPodmanPipes() []string {
|
|||
}
|
||||
|
||||
if len(pipes) == 0 {
|
||||
log.Debug("No Podman machine config files found, falling back to default")
|
||||
return []string{"npipe:////./pipe/podman-machine-default"}
|
||||
}
|
||||
return pipes
|
||||
|
|
@ -58,7 +61,7 @@ func detectPlatformCandidates(log *logrus.Entry) (string, ContainerRuntime, erro
|
|||
}
|
||||
|
||||
// Try Podman machines on Windows
|
||||
for _, podmanHost := range getPodmanPipes() {
|
||||
for _, podmanHost := range getPodmanPipes(log) {
|
||||
err = func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), socketValidationTimeout)
|
||||
defer cancel()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ package commands
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
|
@ -60,3 +62,41 @@ func TestDetectPlatformCandidates_Windows_Failure(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
assert.Equal(t, ErrNoDockerSocket, err)
|
||||
}
|
||||
|
||||
func TestGetPodmanPipes(t *testing.T) {
|
||||
log := logrus.NewEntry(logrus.New())
|
||||
|
||||
// Test fallback when home dir is missing or empty
|
||||
// We can't easily mock UserHomeDir without refactoring, but we can test the logic
|
||||
// by creating the expected directory structure in a temp dir and setting USERPROFILE.
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "podman-test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Set USERPROFILE to our temp dir
|
||||
oldProfile := os.Getenv("USERPROFILE")
|
||||
os.Setenv("USERPROFILE", tmpDir)
|
||||
defer os.Setenv("USERPROFILE", oldProfile)
|
||||
|
||||
// 1. Test fallback when directory doesn't exist
|
||||
pipes := getPodmanPipes(log)
|
||||
assert.Equal(t, []string{"npipe:////./pipe/podman-machine-default"}, pipes)
|
||||
|
||||
// 2. Test with actual config files
|
||||
configDir := filepath.Join(tmpDir, ".config", "containers", "podman", "machine", "wsl")
|
||||
err = os.MkdirAll(configDir, 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(filepath.Join(configDir, "machine1.json"), []byte("{}"), 0644)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(filepath.Join(configDir, "machine2.json"), []byte("{}"), 0644)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(filepath.Join(configDir, "not-a-config.txt"), []byte("{}"), 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pipes = getPodmanPipes(log)
|
||||
assert.Len(t, pipes, 2)
|
||||
assert.Contains(t, pipes, "npipe:////./pipe/machine1")
|
||||
assert.Contains(t, pipes, "npipe:////./pipe/machine2")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue