fix: recover externally removed Talos VMs #29

Merged
eding merged 5 commits from fix/rebuild-absent-vm into main 2026-09-06 10:55:18 +02:00
2 changed files with 45 additions and 29 deletions
Showing only changes of commit 62540c69a2 - Show all commits

View file

@ -40,12 +40,12 @@ const (
) )
type Runner struct { type Runner struct {
Config config.Config Config config.Config
Mode Mode Mode Mode
ConfirmRebuild bool ConfirmRebuild bool
RegisterWebhook bool RegisterWebhook bool
EnableDelivery bool EnableDelivery bool
SkipDeliveryScaffolding bool SkipDeliveryScaffolding bool
AutoMergeBootstrapMigration bool AutoMergeBootstrapMigration bool
} }
@ -1407,7 +1407,7 @@ func terraformReconcileTargets(cfg config.Config) []string {
func (r Runner) rebuildTalosVMs(terraformDir string, environment []string) error { func (r Runner) rebuildTalosVMs(terraformDir string, environment []string) error {
if err := r.importConfiguredTalosVMs(terraformDir, environment); err != nil { if err := r.importConfiguredTalosVMs(terraformDir, environment); err != nil {
var apiErr proxmox.APIError var apiErr proxmox.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == 404 { if errors.As(err, &apiErr) && (apiErr.StatusCode == 404 || apiErr.StatusCode == 500 && apiErr.Message == `{"data":null}` && strings.HasSuffix(apiErr.Path, "/config")) {
return nil return nil
} }
return err return err

View file

@ -1294,31 +1294,47 @@ func TestRebuildTerraformSkipsAlreadyAbsentTalosVM(t *testing.T) {
Nodes: []config.TalosNode{{Name: "cp-01", ProxmoxNode: "pve", VMID: 100, Role: "controlplane"}}, Nodes: []config.TalosNode{{Name: "cp-01", ProxmoxNode: "pve", VMID: 100, Role: "controlplane"}},
}} }}
var events []string var events []string
for _, apiErr := range []proxmox.APIError{{StatusCode: 404}, {StatusCode: 500, Message: `{"data":null}`, Path: "/nodes/pve/qemu/100/config"}} {
verifyTalosVMs = func(config.Config) error {
return fmt.Errorf("inspect configured Talos VM: %w", apiErr)
}
destroyTalosVMs = func(string, []string) error {
t.Fatal("destroy ran for an already-absent VM")
return nil
}
runTerraform = func(_ string, _ []string, name string, args ...string) error {
if name != "terraform" {
t.Fatalf("unexpected command %q", name)
}
switch args[0] {
case "init", "plan", "apply":
events = append(events, args[0])
default:
t.Fatalf("unexpected Terraform operation %q", args[0])
}
return nil
}
if err := (Runner{Config: cfg, Mode: Rebuild}).reconcileTerraform(t.TempDir()); err != nil {
t.Fatal(err)
}
if strings.Join(events, ",") != "init,plan,apply" {
t.Fatalf("Terraform phase order = %q", events)
}
events = nil
}
}
func TestRebuildTerraformRejectsUnexpectedProxmoxError(t *testing.T) {
originalVerify := verifyTalosVMs
t.Cleanup(func() { verifyTalosVMs = originalVerify })
verifyTalosVMs = func(config.Config) error { verifyTalosVMs = func(config.Config) error {
return fmt.Errorf("inspect configured Talos VM: %w", proxmox.APIError{StatusCode: 404}) return fmt.Errorf("inspect configured Talos VM: %w", proxmox.APIError{StatusCode: 500, Message: "server unavailable", Path: "/nodes/pve/qemu/100/config"})
}
destroyTalosVMs = func(string, []string) error {
t.Fatal("destroy ran for an already-absent VM")
return nil
}
runTerraform = func(_ string, _ []string, name string, args ...string) error {
if name != "terraform" {
t.Fatalf("unexpected command %q", name)
}
switch args[0] {
case "init", "plan", "apply":
events = append(events, args[0])
default:
t.Fatalf("unexpected Terraform operation %q", args[0])
}
return nil
} }
if err := (Runner{Config: cfg, Mode: Rebuild}).reconcileTerraform(t.TempDir()); err != nil { err := (Runner{Config: config.Config{Talos: config.TalosConfig{Nodes: []config.TalosNode{{Name: "cp-01", ProxmoxNode: "pve", VMID: 100}}}}, Mode: Rebuild}).rebuildTalosVMs(t.TempDir(), nil)
t.Fatal(err) if err == nil || !strings.Contains(err.Error(), "server unavailable") {
} t.Fatalf("unexpected Proxmox error was accepted: %v", err)
if strings.Join(events, ",") != "init,plan,apply" {
t.Fatalf("Terraform phase order = %q", events)
} }
} }