2026-01-11 02:35:24 -03:00
|
|
|
package plan
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-31 14:52:50 -03:00
|
|
|
"context"
|
2026-01-11 02:35:24 -03:00
|
|
|
"fmt"
|
2026-03-31 14:52:50 -03:00
|
|
|
"os"
|
2026-01-11 02:35:24 -03:00
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
|
|
"metis/pkg/image"
|
|
|
|
|
"metis/pkg/inventory"
|
2026-03-31 14:52:50 -03:00
|
|
|
"metis/pkg/mount"
|
|
|
|
|
"metis/pkg/writer"
|
2026-01-11 02:35:24 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Execute performs a burn if confirm is true. With confirm=false, it only downloads/verifies and returns the plan.
|
|
|
|
|
func Execute(inv *inventory.Inventory, nodeName, device, cacheDir string, confirm bool) (*Plan, error) {
|
|
|
|
|
p, err := Build(inv, nodeName, device, cacheDir)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
cacheImage := filepath.Join(cacheDir, filepath.Base(p.Image))
|
|
|
|
|
if err := image.Download(p.Image, cacheImage); err != nil {
|
|
|
|
|
return p, fmt.Errorf("download image: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := image.VerifyChecksum(cacheImage, checksumFromInventory(inv, nodeName)); err != nil {
|
|
|
|
|
return p, err
|
|
|
|
|
}
|
|
|
|
|
if !confirm {
|
|
|
|
|
return p, nil
|
|
|
|
|
}
|
|
|
|
|
if device == "" || device == "/dev/sdX" {
|
|
|
|
|
return p, fmt.Errorf("refusing to write to placeholder device")
|
|
|
|
|
}
|
2026-03-31 14:52:50 -03:00
|
|
|
ctx := context.Background()
|
|
|
|
|
if err := writer.WriteImage(ctx, cacheImage, device); err != nil {
|
|
|
|
|
return p, fmt.Errorf("write image: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if err := maybeInject(inv, nodeName); err != nil {
|
|
|
|
|
return p, fmt.Errorf("inject config: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if auto := maybeAutoMount(device); auto != nil {
|
|
|
|
|
defer mount.Teardown(auto)
|
|
|
|
|
if err := maybeInject(inv, nodeName); err != nil {
|
|
|
|
|
return p, fmt.Errorf("inject (auto-mount): %w", err)
|
|
|
|
|
}
|
2026-01-11 02:35:24 -03:00
|
|
|
}
|
|
|
|
|
return p, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checksumFromInventory(inv *inventory.Inventory, node string) string {
|
|
|
|
|
_, cls, err := inv.FindNode(node)
|
|
|
|
|
if err != nil || cls == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return cls.Checksum
|
|
|
|
|
}
|
2026-03-31 14:52:50 -03:00
|
|
|
|
|
|
|
|
func maybeAutoMount(device string) *mount.LoopMount {
|
|
|
|
|
if os.Getenv("METIS_AUTO_MOUNT") == "" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
// Use mount helper against the written device partitions.
|
|
|
|
|
m, err := mount.Setup(device)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
// Propagate mount paths for injection.
|
|
|
|
|
if m.BootPath != "" {
|
|
|
|
|
_ = os.Setenv("METIS_BOOT_PATH", m.BootPath)
|
|
|
|
|
}
|
|
|
|
|
if m.RootPath != "" {
|
|
|
|
|
_ = os.Setenv("METIS_ROOT_PATH", m.RootPath)
|
|
|
|
|
}
|
|
|
|
|
return m
|
|
|
|
|
}
|