Files
alexei.dolgolyov 1a8dfefa77 feat: Docker diagnostic hints on disconnection
- Classify Docker errors into categories (socket_not_found, connection_refused,
  permission_denied, timeout, tls_error) with platform-specific hints
- Enrich GET /api/health with structured diagnostics (category, hints, platform)
- Expandable hints panel in sidebar when Docker is disconnected
- "Retry now" button for immediate re-check
- Collapsible raw error details for advanced users
2026-03-30 14:05:00 +03:00

171 lines
4.5 KiB
Go

package docker
import (
"runtime"
"strings"
)
// DiagCategory classifies a Docker connectivity error.
type DiagCategory string
const (
DiagSocketNotFound DiagCategory = "socket_not_found"
DiagConnectionRefused DiagCategory = "connection_refused"
DiagPermissionDenied DiagCategory = "permission_denied"
DiagTimeout DiagCategory = "timeout"
DiagTLSError DiagCategory = "tls_error"
DiagUnknown DiagCategory = "unknown"
)
// Diagnostic holds a classified error with platform-specific hints.
type Diagnostic struct {
Category DiagCategory `json:"category"`
Hints []string `json:"hints"`
Platform string `json:"platform"`
}
// Diagnose classifies a Docker Ping error and returns platform-aware hints.
// Pure function — takes an error and platform, returns structured diagnostics.
func Diagnose(err error, platform string) Diagnostic {
if platform == "" {
platform = runtime.GOOS
}
msg := ""
if err != nil {
msg = strings.ToLower(err.Error())
}
cat := classifyError(msg)
return Diagnostic{
Category: cat,
Hints: hintsFor(cat, platform),
Platform: platform,
}
}
func classifyError(msg string) DiagCategory {
switch {
case containsAny(msg, "no such file or directory", "cannot find the file specified", "the system cannot find"):
return DiagSocketNotFound
case containsAny(msg, "connection refused"):
return DiagConnectionRefused
case containsAny(msg, "permission denied", "access is denied"):
return DiagPermissionDenied
case containsAny(msg, "context deadline exceeded", "i/o timeout", "timeout"):
return DiagTimeout
case containsAny(msg, "tls:", "certificate"):
return DiagTLSError
default:
return DiagUnknown
}
}
func containsAny(s string, substrs ...string) bool {
for _, sub := range substrs {
if strings.Contains(s, sub) {
return true
}
}
return false
}
var hintTable = map[DiagCategory]map[string][]string{
DiagSocketNotFound: {
"windows": {
"Docker Desktop does not appear to be running.",
"Start Docker Desktop from the Start Menu or system tray.",
"If using a custom socket path, check the DOCKER_HOST environment variable.",
},
"linux": {
"Docker daemon is not running.",
"Start it with: sudo systemctl start docker",
"If using a custom socket path, check the DOCKER_HOST environment variable.",
},
"darwin": {
"Docker Desktop does not appear to be running.",
"Start it from Applications or run: open -a Docker",
"If using a custom socket path, check the DOCKER_HOST environment variable.",
},
},
DiagConnectionRefused: {
"windows": {
"Docker Desktop is starting up — wait ~30 seconds and retry.",
"If it persists, restart Docker Desktop.",
},
"linux": {
"Docker daemon is starting up.",
"Check status with: sudo systemctl status docker",
},
"darwin": {
"Docker Desktop is starting up.",
"Check the whale icon in the menu bar for status.",
},
},
DiagPermissionDenied: {
"windows": {
"Run the application as Administrator, or add your user to the docker-users group.",
},
"linux": {
"Add your user to the docker group: sudo usermod -aG docker $USER",
"Then log out and log back in for the change to take effect.",
},
"darwin": {
"Check Docker Desktop settings under Resources > File Sharing.",
},
},
DiagTimeout: {
"windows": {
"Docker Desktop may be overloaded or hanging.",
"Try restarting Docker Desktop.",
},
"linux": {
"Docker daemon may be overloaded.",
"Check logs with: journalctl -u docker --no-pager -n 50",
},
"darwin": {
"Docker Desktop may be unresponsive.",
"Restart it from the menu bar whale icon.",
},
},
DiagTLSError: {
"windows": {
"Check Docker TLS certificate configuration.",
"Verify the DOCKER_TLS_VERIFY environment variable.",
},
"linux": {
"Verify certificates in ~/.docker/ match the daemon configuration.",
},
"darwin": {
"Check ~/.docker/ TLS configuration.",
},
},
DiagUnknown: {
"windows": {
"An unexpected Docker error occurred.",
"Try restarting Docker Desktop.",
},
"linux": {
"An unexpected Docker error occurred.",
"Check daemon status with: sudo systemctl status docker",
},
"darwin": {
"An unexpected Docker error occurred.",
"Try restarting Docker Desktop.",
},
},
}
func hintsFor(cat DiagCategory, platform string) []string {
catHints, ok := hintTable[cat]
if !ok {
catHints = hintTable[DiagUnknown]
}
hints, ok := catHints[platform]
if !ok {
hints = catHints["linux"] // fallback
}
return hints
}