lazydocker/pkg/commands/socket_detection_unix.go
DeiAsPie 81e4e44309
Enhance Podman support with improved socket detection and error handling across platforms
Signed-off-by: DeiAsPie <93835541+DeiAsPie@users.noreply.github.com>
2025-12-27 14:56:14 +05:30

163 lines
4.8 KiB
Go

//go:build !windows
package commands
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
var (
statFunc = os.Stat
getuidFunc = os.Getuid
getenvFunc = os.Getenv
userHomeDirFunc = os.UserHomeDir
)
const (
DockerSocketSchema = "unix://"
DockerSocketPath = "/var/run/docker.sock"
defaultDockerHost = DockerSocketSchema + DockerSocketPath
)
// SocketCandidate represents a potential socket to try
type SocketCandidate struct {
Path string
Runtime ContainerRuntime
}
// getSocketCandidates returns all possible socket paths in priority order
func getSocketCandidates() []SocketCandidate {
var candidates []SocketCandidate
// Helper to add candidate if path is valid
addCandidate := func(path string, runtime ContainerRuntime) {
if path != "" {
candidates = append(candidates, SocketCandidate{
Path: DockerSocketSchema + path,
Runtime: runtime,
})
}
}
// 1. Standard Docker daemon socket
addCandidate(DockerSocketPath, RuntimeDocker)
xdgRuntime := getenvFunc("XDG_RUNTIME_DIR")
home, _ := userHomeDirFunc()
uid := getuidFunc()
// 2. Rootless Docker: $XDG_RUNTIME_DIR/docker.sock
if xdgRuntime != "" {
addCandidate(filepath.Join(xdgRuntime, "docker.sock"), RuntimeDocker)
}
// 3. Rootless Docker: ~/.docker/run/docker.sock
if home != "" {
addCandidate(filepath.Join(home, ".docker", "run", "docker.sock"), RuntimeDocker)
// 4. Docker Desktop: ~/.docker/desktop/docker.sock
addCandidate(filepath.Join(home, ".docker", "desktop", "docker.sock"), RuntimeDocker)
}
// 5. Rootless Docker: /run/user/$UID/docker.sock
addCandidate(filepath.Join("/run", "user", strconv.Itoa(uid), "docker.sock"), RuntimeDocker)
// 6. Colima
if home != "" {
addCandidate(filepath.Join(home, ".colima", "default", "docker.sock"), RuntimeDocker)
addCandidate(filepath.Join(home, ".colima", "docker.sock"), RuntimeDocker)
}
// 7. OrbStack
if home != "" {
addCandidate(filepath.Join(home, ".orbstack", "run", "docker.sock"), RuntimeDocker)
}
// 8. Lima
if home != "" {
addCandidate(filepath.Join(home, ".lima", "default", "sock", "docker.sock"), RuntimeDocker)
}
// 9. Rancher Desktop
if home != "" {
addCandidate(filepath.Join(home, ".rd", "docker.sock"), RuntimeDocker)
}
// 10. Snap Docker
addCandidate("/var/snap/docker/current/run/docker.sock", RuntimeDocker)
// 11. Rootless Podman: $XDG_RUNTIME_DIR/podman/podman.sock
if xdgRuntime != "" {
addCandidate(filepath.Join(xdgRuntime, "podman", "podman.sock"), RuntimePodman)
}
// 12. Rootless Podman: /run/user/$UID/podman/podman.sock
addCandidate(filepath.Join("/run", "user", strconv.Itoa(uid), "podman", "podman.sock"), RuntimePodman)
// 13. Rootless Podman: ~/.local/share/containers/podman/podman.sock
if home != "" {
addCandidate(filepath.Join(home, ".local", "share", "containers", "podman", "podman.sock"), RuntimePodman)
}
// 14. Rootful Podman: /run/podman/podman.sock
addCandidate("/run/podman/podman.sock", RuntimePodman)
// 15. Podman on macOS (default machine)
if home != "" {
addCandidate(filepath.Join(home, ".local", "share", "containers", "podman", "machine", "podman-machine-default", "podman.sock"), RuntimePodman)
addCandidate(filepath.Join(home, ".local", "share", "containers", "podman", "machine", "qemu", "podman.sock"), RuntimePodman)
}
return candidates
}
func detectPlatformCandidates(log *logrus.Entry) (string, ContainerRuntime, error) {
var lastErr error
candidates := getSocketCandidates()
for _, candidate := range candidates {
socketPath := strings.TrimPrefix(candidate.Path, DockerSocketSchema)
// Fast path: check if socket file exists
if _, err := statFunc(socketPath); err != nil {
continue
}
// Validate by actually connecting
ctx, cancel := context.WithTimeout(context.Background(), socketValidationTimeout)
err := func() error {
defer cancel()
return validateSocketFunc(ctx, candidate.Path, false)
}()
if err != nil {
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 %v group?)", candidate.Path, candidate.Runtime)
} else {
lastErr = fmt.Errorf("%s: %v", candidate.Path, err)
}
continue
}
log.Infof("Connected to %s runtime via %s", candidate.Runtime, candidate.Path)
return candidate.Path, candidate.Runtime, nil
}
// All candidates failed - provide actionable error
if lastErr != nil {
return "", RuntimeUnknown, fmt.Errorf("%w: last error: %v", ErrNoDockerSocket, lastErr)
}
msg := fmt.Sprintf("%v: ensure Docker or Podman is running", ErrNoDockerSocket)
return "", RuntimeUnknown, errors.New(msg)
}