gui/pkg/container/detect.go

102 lines
2.5 KiB
Go
Raw Normal View History

package container
import (
"os/exec"
"runtime"
"strconv"
"strings"
)
// ContainerRuntime describes the preferred isolated workload runtime for CoreGUI.
//
// runtime := container.Detect()
// if runtime == container.RuntimeApple { /* prefer Apple Containers */ }
type ContainerRuntime string
const (
RuntimeNone ContainerRuntime = ""
RuntimeApple ContainerRuntime = "apple"
RuntimeDocker ContainerRuntime = "docker"
RuntimePodman ContainerRuntime = "podman"
)
// DetectEnvironment controls runtime detection for tests and other callers.
//
// runtime := container.DetectWithEnvironment(container.DetectEnvironment{
// GOOS: "darwin",
// ProductVersion: "26.0",
// })
type DetectEnvironment struct {
GOOS string
ProductVersion string
LookPath func(file string) (string, error)
}
// Detect prefers Apple Containers on macOS 26+, then Docker, then Podman.
//
// runtime := container.Detect()
func Detect() ContainerRuntime {
environment := DetectEnvironment{
GOOS: runtime.GOOS,
ProductVersion: "",
LookPath: exec.LookPath,
}
if runtime.GOOS == "darwin" {
environment.ProductVersion = productVersion()
}
return DetectWithEnvironment(environment)
}
// DetectWithEnvironment applies the RFC runtime ordering using an explicit environment.
//
// runtime := container.DetectWithEnvironment(env)
func DetectWithEnvironment(environment DetectEnvironment) ContainerRuntime {
lookPath := environment.LookPath
if lookPath == nil {
lookPath = exec.LookPath
}
goos := strings.ToLower(strings.TrimSpace(environment.GOOS))
if goos == "darwin" && majorVersion(environment.ProductVersion) >= 26 {
if hasBinary(lookPath, "container") || hasBinary(lookPath, "apple-container") || hasBinary(lookPath, "containerctl") {
return RuntimeApple
}
}
if hasBinary(lookPath, "docker") {
return RuntimeDocker
}
if hasBinary(lookPath, "podman") {
return RuntimePodman
}
return RuntimeNone
}
func hasBinary(lookPath func(string) (string, error), binary string) bool {
if strings.TrimSpace(binary) == "" {
return false
}
_, err := lookPath(binary)
return err == nil
}
func majorVersion(productVersion string) int {
productVersion = strings.TrimSpace(productVersion)
if productVersion == "" {
return 0
}
major, _, _ := strings.Cut(productVersion, ".")
value, err := strconv.Atoi(major)
if err != nil {
return 0
}
return value
}
func productVersion() string {
output, err := exec.Command("sw_vers", "-productVersion").Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(output))
}