Move avoidable daemon shell-outs into Go
Reduce the control plane's dependency on helper scripts while keeping the hard Linux integration points in the approved shell-out layer. Replace the bash-driven image build path with a native Go builder that clones and optionally resizes the rootfs, boots a temporary Firecracker VM, provisions the guest over SSH, installs packages and modules, and preserves the package-manifest sidecar. Also replace a few small convenience shell-outs with Go helpers: read process stats from /proc, use os.Truncate for ext4 image growth, add file-clone and normalized-line helpers, drop the sh -c work-disk flattening path, and launch Firecracker via a direct sudo command. Add tests for the new SSH/archive and system helpers, plus a policy test that keeps os/exec imports confined to cli/firecracker/system. Update the docs to describe customize.sh as a manual helper rather than the daemon's image-build backend. Validated with go mod tidy, go test ./..., and make build.
This commit is contained in:
parent
0a0b0b617b
commit
942d242c03
17 changed files with 936 additions and 145 deletions
|
|
@ -8,6 +8,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -105,25 +106,48 @@ type ProcessStats struct {
|
|||
}
|
||||
|
||||
func ReadProcessStats(ctx context.Context, pid int) (ProcessStats, error) {
|
||||
_ = ctx
|
||||
if pid <= 0 {
|
||||
return ProcessStats{}, errors.New("pid is required")
|
||||
}
|
||||
runner := NewRunner()
|
||||
out, err := runner.Run(ctx, "ps", "-p", strconv.Itoa(pid), "-o", "%cpu=,rss=,vsz=")
|
||||
statData, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
|
||||
if err != nil {
|
||||
return ProcessStats{}, err
|
||||
}
|
||||
fields := strings.Fields(string(out))
|
||||
if len(fields) < 3 {
|
||||
return ProcessStats{}, fmt.Errorf("unexpected ps output: %q", string(out))
|
||||
statmData, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "statm"))
|
||||
if err != nil {
|
||||
return ProcessStats{}, err
|
||||
}
|
||||
cpu, _ := strconv.ParseFloat(fields[0], 64)
|
||||
rssKB, _ := strconv.ParseInt(fields[1], 10, 64)
|
||||
vszKB, _ := strconv.ParseInt(fields[2], 10, 64)
|
||||
uptimeData, err := os.ReadFile("/proc/uptime")
|
||||
if err != nil {
|
||||
return ProcessStats{}, err
|
||||
}
|
||||
|
||||
procStat, err := parseProcStat(string(statData))
|
||||
if err != nil {
|
||||
return ProcessStats{}, err
|
||||
}
|
||||
memStat, err := parseProcStatm(string(statmData))
|
||||
if err != nil {
|
||||
return ProcessStats{}, err
|
||||
}
|
||||
uptimeSeconds, err := parseProcUptime(string(uptimeData))
|
||||
if err != nil {
|
||||
return ProcessStats{}, err
|
||||
}
|
||||
|
||||
const ticksPerSecond = 100.0
|
||||
elapsedSeconds := uptimeSeconds - (float64(procStat.startTicks) / ticksPerSecond)
|
||||
cpuPercent := 0.0
|
||||
if elapsedSeconds > 0 {
|
||||
totalCPUSeconds := float64(procStat.userTicks+procStat.systemTicks) / ticksPerSecond
|
||||
cpuPercent = math.Max(0, (totalCPUSeconds/elapsedSeconds)*100)
|
||||
}
|
||||
pageSize := int64(os.Getpagesize())
|
||||
return ProcessStats{
|
||||
CPUPercent: cpu,
|
||||
RSSBytes: rssKB * 1024,
|
||||
VSZBytes: vszKB * 1024,
|
||||
CPUPercent: cpuPercent,
|
||||
RSSBytes: memStat.residentPages * pageSize,
|
||||
VSZBytes: memStat.sizePages * pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -179,7 +203,7 @@ func CopyDirContents(ctx context.Context, runner CommandRunner, sourceDir, targe
|
|||
}
|
||||
|
||||
func ResizeExt4Image(ctx context.Context, runner CommandRunner, path string, bytes int64) error {
|
||||
if _, err := runner.Run(ctx, "truncate", "-s", strconv.FormatInt(bytes, 10), path); err != nil {
|
||||
if err := os.Truncate(path, bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := runner.Run(ctx, "e2fsck", "-p", "-f", path); err != nil {
|
||||
|
|
@ -324,3 +348,67 @@ func CopyStream(dst io.Writer, cmd *exec.Cmd) error {
|
|||
cmd.Stdin = os.Stdin
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
type procStat struct {
|
||||
userTicks uint64
|
||||
systemTicks uint64
|
||||
startTicks uint64
|
||||
}
|
||||
|
||||
type procStatm struct {
|
||||
sizePages int64
|
||||
residentPages int64
|
||||
}
|
||||
|
||||
func parseProcStat(raw string) (procStat, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
end := strings.LastIndex(raw, ")")
|
||||
if end == -1 || end+2 >= len(raw) {
|
||||
return procStat{}, fmt.Errorf("unexpected /proc stat format: %q", raw)
|
||||
}
|
||||
fields := strings.Fields(raw[end+2:])
|
||||
if len(fields) < 20 {
|
||||
return procStat{}, fmt.Errorf("unexpected /proc stat field count: %q", raw)
|
||||
}
|
||||
userTicks, err := strconv.ParseUint(fields[11], 10, 64)
|
||||
if err != nil {
|
||||
return procStat{}, err
|
||||
}
|
||||
systemTicks, err := strconv.ParseUint(fields[12], 10, 64)
|
||||
if err != nil {
|
||||
return procStat{}, err
|
||||
}
|
||||
startTicks, err := strconv.ParseUint(fields[19], 10, 64)
|
||||
if err != nil {
|
||||
return procStat{}, err
|
||||
}
|
||||
return procStat{
|
||||
userTicks: userTicks,
|
||||
systemTicks: systemTicks,
|
||||
startTicks: startTicks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseProcStatm(raw string) (procStatm, error) {
|
||||
fields := strings.Fields(strings.TrimSpace(raw))
|
||||
if len(fields) < 2 {
|
||||
return procStatm{}, fmt.Errorf("unexpected /proc statm format: %q", raw)
|
||||
}
|
||||
sizePages, err := strconv.ParseInt(fields[0], 10, 64)
|
||||
if err != nil {
|
||||
return procStatm{}, err
|
||||
}
|
||||
residentPages, err := strconv.ParseInt(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return procStatm{}, err
|
||||
}
|
||||
return procStatm{sizePages: sizePages, residentPages: residentPages}, nil
|
||||
}
|
||||
|
||||
func parseProcUptime(raw string) (float64, error) {
|
||||
fields := strings.Fields(strings.TrimSpace(raw))
|
||||
if len(fields) == 0 {
|
||||
return 0, fmt.Errorf("unexpected /proc uptime format: %q", raw)
|
||||
}
|
||||
return strconv.ParseFloat(fields[0], 64)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue