- 意图命令按 OS/架构、Linux 发行版与容器运行时解析并执行对应底层命令 - 内置 docker.clearsafe、net.ports、pkg.refreshindex - --dry-run 预览、list/explain/platform/version 辅助命令 - 纯 Go 跨平台构建脚本 scripts/build
119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
// Package platform detects the execution environment that kopsc uses to pick
|
|
// the most specific command variant: OS/arch, Linux distribution and available
|
|
// container runtimes.
|
|
package platform
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// OSReleasePath is where Linux distributions publish their identity.
|
|
const OSReleasePath = "/etc/os-release"
|
|
|
|
// Info is the detected environment.
|
|
type Info struct {
|
|
OS string // runtime.GOOS, e.g. "linux", "darwin", "windows"
|
|
Arch string // runtime.GOARCH, e.g. "amd64", "arm64"
|
|
Distro string // lower-case os-release ID, empty when unknown
|
|
DistroLike []string // lower-case os-release ID_LIKE entries
|
|
Runtimes []string // container runtimes found on PATH, e.g. "docker", "podman"
|
|
}
|
|
|
|
// Probe gathers Info. Its dependencies are injectable so detection can be
|
|
// exercised without touching the real host.
|
|
type Probe struct {
|
|
GOOS string
|
|
GOARCH string
|
|
ReadFile func(string) ([]byte, error)
|
|
LookPath func(string) (string, error)
|
|
RuntimeCandidates []string
|
|
}
|
|
|
|
// System returns a Probe wired to the real process environment.
|
|
func System() Probe {
|
|
return Probe{
|
|
GOOS: runtime.GOOS,
|
|
GOARCH: runtime.GOARCH,
|
|
ReadFile: os.ReadFile,
|
|
LookPath: exec.LookPath,
|
|
RuntimeCandidates: []string{"docker", "podman"},
|
|
}
|
|
}
|
|
|
|
// Detect inspects the environment. A missing or unreadable os-release file is
|
|
// not an error: it simply leaves Distro empty, which resolution treats as
|
|
// "unknown distribution".
|
|
func (p Probe) Detect() Info {
|
|
info := Info{OS: p.GOOS, Arch: p.GOARCH}
|
|
|
|
if p.GOOS == "linux" && p.ReadFile != nil {
|
|
if data, err := p.ReadFile(OSReleasePath); err == nil {
|
|
info.Distro, info.DistroLike = ParseOSRelease(data)
|
|
}
|
|
}
|
|
|
|
candidates := p.RuntimeCandidates
|
|
if candidates == nil {
|
|
candidates = []string{"docker", "podman"}
|
|
}
|
|
if p.LookPath != nil {
|
|
for _, name := range candidates {
|
|
if _, err := p.LookPath(name); err == nil {
|
|
info.Runtimes = append(info.Runtimes, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// ParseOSRelease extracts ID and ID_LIKE from os-release content. Values are
|
|
// lower-cased and unquoted; parsing never fails, malformed lines are skipped.
|
|
func ParseOSRelease(data []byte) (distro string, like []string) {
|
|
values := make(map[string]string)
|
|
scanner := bufio.NewScanner(bytes.NewReader(data))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
key, value, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
continue
|
|
}
|
|
values[strings.TrimSpace(key)] = strings.Trim(strings.TrimSpace(value), `"'`)
|
|
}
|
|
|
|
distro = strings.ToLower(strings.TrimSpace(values["ID"]))
|
|
for _, field := range strings.Fields(values["ID_LIKE"]) {
|
|
like = append(like, strings.ToLower(field))
|
|
}
|
|
return distro, like
|
|
}
|
|
|
|
// String renders Info for `kopsc platform`.
|
|
func (i Info) String() string {
|
|
var b strings.Builder
|
|
b.WriteString("os: " + i.OS + "\n")
|
|
b.WriteString("arch: " + i.Arch + "\n")
|
|
distro := i.Distro
|
|
if distro == "" {
|
|
distro = "-"
|
|
}
|
|
b.WriteString("distro: " + distro + "\n")
|
|
if len(i.DistroLike) > 0 {
|
|
b.WriteString("like: " + strings.Join(i.DistroLike, " ") + "\n")
|
|
}
|
|
runtimes := "-"
|
|
if len(i.Runtimes) > 0 {
|
|
runtimes = strings.Join(i.Runtimes, ", ")
|
|
}
|
|
b.WriteString("runtimes: " + runtimes + "\n")
|
|
return b.String()
|
|
}
|