From 2852569aa3aeb3b4180a938a11779c8e0a640986 Mon Sep 17 00:00:00 2001 From: eding Date: Sun, 23 Aug 2026 19:43:06 +0200 Subject: [PATCH] fix: accept empty Terraform state imports --- internal/bootstrap/bootstrap.go | 26 ++++++++++++++++++++++++-- internal/bootstrap/bootstrap_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 27afee6..a11eb04 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -86,17 +86,39 @@ var runGitEnvironment = func(dir string, environment []string, args ...string) ( var verifyTalosVMs = verifyConfiguredTalosVMs -var terraformStateResources = func(terraformDir string, environment []string) ([]string, error) { +var terraformStateResources = listTerraformStateResources + +var runTerraformStateList = func(terraformDir string, environment []string) ([]byte, []byte, error) { command := exec.Command("terraform", "state", "list") command.Dir = terraformDir command.Env = append(os.Environ(), environment...) - state, err := command.Output() + var stdout, stderr bytes.Buffer + command.Stdout = &stdout + command.Stderr = &stderr + err := command.Run() + return stdout.Bytes(), stderr.Bytes(), err +} + +func listTerraformStateResources(terraformDir string, environment []string) ([]string, error) { + state, stderr, err := runTerraformStateList(terraformDir, environment) if err != nil { + if terraformNoStateFile(stderr) { + return nil, nil + } return nil, fmt.Errorf("list Terraform state: %w", err) } return strings.Fields(string(state)), nil } +func terraformNoStateFile(stderr []byte) bool { + for _, line := range strings.Split(string(stderr), "\n") { + if strings.TrimSpace(line) == "No state file was found!" { + return true + } + } + return false +} + var destroyTalosVMs = func(terraformDir string, environment []string) error { return utils.RunCommandInDirEnv(terraformDir, environment, "terraform", "destroy", "-auto-approve", "-input=false", "-target=proxmox_virtual_environment_vm.vm") } diff --git a/internal/bootstrap/bootstrap_test.go b/internal/bootstrap/bootstrap_test.go index 9f44193..1c8b72e 100644 --- a/internal/bootstrap/bootstrap_test.go +++ b/internal/bootstrap/bootstrap_test.go @@ -911,6 +911,31 @@ func TestTerraformPlanPathIsAbsolute(t *testing.T) { } } +func TestTerraformStateResourcesAllowsNoStateFile(t *testing.T) { + original := runTerraformStateList + t.Cleanup(func() { runTerraformStateList = original }) + runTerraformStateList = func(string, []string) ([]byte, []byte, error) { + return nil, []byte("No state file was found!\n"), errors.New("exit status 1") + } + + resources, err := listTerraformStateResources("terraform", nil) + if err != nil || len(resources) != 0 { + t.Fatalf("no-state Terraform result = %q, %v; want empty resources and nil error", resources, err) + } +} + +func TestTerraformStateResourcesRejectsUnexpectedStateError(t *testing.T) { + original := runTerraformStateList + t.Cleanup(func() { runTerraformStateList = original }) + runTerraformStateList = func(string, []string) ([]byte, []byte, error) { + return nil, []byte("Error: failed to load backend\n"), errors.New("exit status 1") + } + + if _, err := listTerraformStateResources("terraform", nil); err == nil || !strings.Contains(err.Error(), "list Terraform state") { + t.Fatalf("unexpected Terraform state error was accepted: %v", err) + } +} + func TestVerifyStateTalosVMsRejectsForeignAndMissingVMs(t *testing.T) { cfg := config.Config{Talos: config.TalosConfig{Nodes: []config.TalosNode{{Name: "cp-01", ProxmoxNode: "pve", VMID: 100}, {Name: "worker-01", ProxmoxNode: "pve", VMID: 101}}}} if err := verifyStateTalosVMs([]string{`proxmox_virtual_environment_vm.vm["cp-01"]`, `proxmox_virtual_environment_vm.vm["foreign"]`}, cfg, false); err == nil || !strings.Contains(err.Error(), "unconfigured") {