98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
package docker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/docker/client"
|
|
)
|
|
|
|
// EngineInfo — runtime yang benar-benar terdeteksi (Docker ATAU Podman).
|
|
type EngineInfo struct {
|
|
Name string // "docker" | "podman"
|
|
Host string // unix:///… atau tcp://… (kosong = default/env DOCKER_HOST)
|
|
}
|
|
|
|
// candidate — endpoint yang dicoba berurutan saat deteksi.
|
|
type candidate struct {
|
|
name string
|
|
host string // kosong = biarkan env/DOCKER_HOST yang menentukan
|
|
}
|
|
|
|
// engineCandidates — urutan kandidat: override config → DOCKER_HOST →
|
|
// docker default → podman rootless (XDG) → podman rootful.
|
|
func engineCandidates(override string) []candidate {
|
|
switch {
|
|
case override != "":
|
|
return []candidate{{name: nameFromHost(override), host: override}}
|
|
case os.Getenv("DOCKER_HOST") != "":
|
|
h := os.Getenv("DOCKER_HOST")
|
|
return []candidate{{name: nameFromHost(h), host: ""}}
|
|
}
|
|
out := []candidate{{name: "docker", host: ""}}
|
|
if xdg := os.Getenv("XDG_RUNTIME_DIR"); xdg != "" {
|
|
out = append(out, candidate{
|
|
name: "podman",
|
|
host: "unix://" + filepath.Join(xdg, "podman", "podman.sock"),
|
|
})
|
|
}
|
|
out = append(out, candidate{name: "podman", host: "unix:///run/podman/podman.sock"})
|
|
return out
|
|
}
|
|
|
|
func nameFromHost(host string) string {
|
|
if strings.Contains(host, "podman") {
|
|
return "podman"
|
|
}
|
|
return "docker"
|
|
}
|
|
|
|
// Detect — client pertama yang ping-nya hidup, plus info runtime-nya.
|
|
// Podman mengekspos API kompatibel-Docker di podman.sock, jadi client yang
|
|
// sama dipakai untuk keduanya.
|
|
func Detect(override string) (*Client, EngineInfo, error) {
|
|
connect := func(host string) (*Client, error) {
|
|
opts := []client.Opt{client.WithAPIVersionNegotiation()}
|
|
if host == "" {
|
|
opts = append(opts, client.FromEnv)
|
|
} else {
|
|
opts = append(opts, client.WithHost(host))
|
|
}
|
|
cli, err := client.NewClientWithOpts(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
if _, err := cli.Ping(ctx); err != nil {
|
|
_ = cli.Close()
|
|
return nil, err
|
|
}
|
|
return &Client{cli: cli}, nil
|
|
}
|
|
return detectWith(engineCandidates(override), connect)
|
|
}
|
|
|
|
// detectWith — inti deteksi; connect dapat di-stub di unit test.
|
|
func detectWith(cands []candidate, connect func(host string) (*Client, error)) (*Client, EngineInfo, error) {
|
|
var lastErr error
|
|
for _, c := range cands {
|
|
cl, err := connect(c.host)
|
|
if err == nil {
|
|
return cl, EngineInfo{Name: c.name, Host: c.host}, nil
|
|
}
|
|
lastErr = err
|
|
}
|
|
return nil, EngineInfo{}, fmt.Errorf("engine: tidak ada Docker/Podman yang merespons: %w", lastErr)
|
|
}
|
|
|
|
// New — pintasan auto-detect (kompatibilitas; info runtime diabaikan).
|
|
func New() (*Client, error) {
|
|
cl, _, err := Detect("")
|
|
return cl, err
|
|
}
|