metis/pkg/plan/plan.go

69 lines
2.3 KiB
Go

package plan
import (
"fmt"
"os"
"path/filepath"
"time"
"metis/pkg/inventory"
)
// Action describes a step in the burn process.
type Action struct {
Type string `json:"type"`
Detail string `json:"detail"`
Command string `json:"command,omitempty"`
}
// Plan describes the overall burn for a node.
type Plan struct {
Node string `json:"node"`
Device string `json:"device"`
Image string `json:"image"`
Class string `json:"class"`
Actions []Action `json:"actions"`
}
// Build constructs a plan without executing it.
func Build(inv *inventory.Inventory, nodeName, device, cacheDir string) (*Plan, error) {
node, class, err := inv.FindNode(nodeName)
if err != nil {
return nil, err
}
if device == "" {
device = "/dev/sdX" // placeholder
}
cacheImage := filepath.Join(cacheDir, filepath.Base(class.Image))
actions := []Action{
{Type: "fetch", Detail: fmt.Sprintf("Download %s to %s", class.Image, cacheImage)},
}
if class.Checksum != "" {
actions = append(actions, Action{Type: "verify", Detail: fmt.Sprintf("Verify checksum %s", class.Checksum)})
}
actions = append(actions, Action{Type: "write", Detail: fmt.Sprintf("Write image to %s", device), Command: fmt.Sprintf("dd if=%s of=%s bs=4M status=progress conv=fsync", cacheImage, device)})
if boot := os.Getenv("METIS_BOOT_PATH"); boot != "" {
actions = append(actions, Action{Type: "inject", Detail: fmt.Sprintf("Inject config into boot mount %s", boot)})
}
if root := os.Getenv("METIS_ROOT_PATH"); root != "" {
actions = append(actions, Action{Type: "inject", Detail: fmt.Sprintf("Inject config into root mount %s", root)})
}
if os.Getenv("METIS_BOOT_PATH") == "" && os.Getenv("METIS_ROOT_PATH") == "" {
actions = append(actions, Action{Type: "inject", Detail: "Inject hostname/network/k3s config (requires mounted boot/root; skipped if unset)"})
}
actions = append(actions, Action{Type: "finalize", Detail: fmt.Sprintf("Ready to insert SD for %s", node.Hostname)})
return &Plan{
Node: nodeName,
Device: device,
Image: class.Image,
Class: class.Name,
Actions: actions,
}, nil
}
// NextRunStale returns true if the last success was older than the given duration.
func NextRunStale(lastSuccess time.Time, maxAge time.Duration) bool {
return time.Since(lastSuccess) > maxAge
}