package plan import ( "fmt" "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)}) actions = append(actions, Action{Type: "inject", Detail: "Inject hostname/network/k3s config into boot or rootfs"}) 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 }