diff --git a/backend/local/backend_local.go b/backend/local/backend_local.go index 4ac20013e..acb5215af 100644 --- a/backend/local/backend_local.go +++ b/backend/local/backend_local.go @@ -163,6 +163,7 @@ func (b *Local) contextFromPlanFile(pf *planfile.Reader, opts terraform.ContextO errSummary, fmt.Sprintf("Failed to read plan from plan file: %s.", err), )) + return nil, snap, diags } variables := terraform.InputValues{} diff --git a/command/apply.go b/command/apply.go index de7ca3207..36a8d5cae 100644 --- a/command/apply.go +++ b/command/apply.go @@ -137,6 +137,18 @@ func (c *ApplyCommand) Run(args []string) int { "Failed to read plan from plan file", fmt.Sprintf("Cannot read the plan from the given plan file: %s.", err), )) + c.showDiagnostics(diags) + return 1 + } + if plan.Backend.Config == nil { + // Should never happen; always indicates a bug in the creation of the plan file + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to read plan from plan file", + fmt.Sprintf("The given plan file does not have a valid backend configuration. This is a bug in the Terraform command that generated this plan file."), + )) + c.showDiagnostics(diags) + return 1 } be, beDiags = c.BackendForPlan(plan.Backend) } diff --git a/command/apply_test.go b/command/apply_test.go index 25a57ce8b..70a6b03f4 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -108,6 +108,7 @@ func TestApply_lockedState(t *testing.T) { // test apply with locked state, waiting for unlock func TestApply_lockedStateWait(t *testing.T) { + t.Fatalf("FIXME: this test seems to be making the test program prematurely exit") statePath := testTempFile(t) unlock, err := testLockState("./testdata", statePath) diff --git a/command/state_list_test.go b/command/state_list_test.go index 0d4cedd00..7a7a6dfe9 100644 --- a/command/state_list_test.go +++ b/command/state_list_test.go @@ -14,7 +14,7 @@ func TestStateList(t *testing.T) { statePath := testStateFile(t, state) p := testProvider() - ui := new(cli.MockUi) + ui := cli.NewMockUi() c := &StateListCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), @@ -42,7 +42,7 @@ func TestStateListWithID(t *testing.T) { statePath := testStateFile(t, state) p := testProvider() - ui := new(cli.MockUi) + ui := cli.NewMockUi() c := &StateListCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), @@ -71,7 +71,7 @@ func TestStateListWithNonExistentID(t *testing.T) { statePath := testStateFile(t, state) p := testProvider() - ui := new(cli.MockUi) + ui := cli.NewMockUi() c := &StateListCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), @@ -104,7 +104,7 @@ func TestStateList_backendState(t *testing.T) { defer testChdir(t, td)() p := testProvider() - ui := new(cli.MockUi) + ui := cli.NewMockUi() c := &StateListCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), @@ -130,7 +130,7 @@ func TestStateList_noState(t *testing.T) { defer testFixCwd(t, tmp, cwd) p := testProvider() - ui := new(cli.MockUi) + ui := cli.NewMockUi() c := &StateListCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), diff --git a/command/state_push_test.go b/command/state_push_test.go index e7fd5f446..21115065c 100644 --- a/command/state_push_test.go +++ b/command/state_push_test.go @@ -194,6 +194,7 @@ func TestStatePush_serialOlder(t *testing.T) { } func TestStatePush_forceRemoteState(t *testing.T) { + t.Fatalf("FIXME: This test seems to be getting hanged or into an infinite loop") td := tempDir(t) copy.CopyDir(testFixturePath("inmem-backend"), td) defer os.RemoveAll(td) diff --git a/command/state_test.go b/command/state_test.go index 433c6336b..49762075e 100644 --- a/command/state_test.go +++ b/command/state_test.go @@ -6,7 +6,7 @@ import ( "sort" "testing" - "github.com/hashicorp/terraform/state" + "github.com/hashicorp/terraform/states/statemgr" ) // testStateBackups returns the list of backups in order of creation @@ -33,7 +33,7 @@ func TestStateDefaultBackupExtension(t *testing.T) { t.Fatal(err) } - backupPath := s.(*state.BackupState).Path + backupPath := s.(*statemgr.Filesystem).BackupPath() match := regexp.MustCompile(`terraform\.tfstate\.\d+\.backup$`).MatchString if !match(backupPath) { t.Fatal("Bad backup path:", backupPath) diff --git a/command/workspace_command_test.go b/command/workspace_command_test.go index 4503850fd..146c21aba 100644 --- a/command/workspace_command_test.go +++ b/command/workspace_command_test.go @@ -215,6 +215,7 @@ func TestWorkspace_createInvalid(t *testing.T) { } func TestWorkspace_createWithState(t *testing.T) { + t.Fatalf("FIXME: This test seems to be getting hung or into an infinite loop") td := tempDir(t) copy.CopyDir(testFixturePath("inmem-backend"), td) defer os.RemoveAll(td) diff --git a/plans/planfile/tfplan.go b/plans/planfile/tfplan.go index 0474f97e3..2db909b9f 100644 --- a/plans/planfile/tfplan.go +++ b/plans/planfile/tfplan.go @@ -287,6 +287,13 @@ func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) { // writeTfplan serializes the given plan into the protobuf-based format used // for the "tfplan" portion of a plan file. func writeTfplan(plan *plans.Plan, w io.Writer) error { + if plan == nil { + return fmt.Errorf("cannot write plan file for nil plan") + } + if plan.Changes == nil { + return fmt.Errorf("cannot write plan file with nil changeset") + } + rawPlan := &planproto.Plan{ Version: tfplanFormatVersion, TerraformVersion: version.String(), diff --git a/states/state.go b/states/state.go index c6d005a11..1f842359e 100644 --- a/states/state.go +++ b/states/state.go @@ -47,6 +47,9 @@ func BuildState(cb func(*SyncState)) *State { // in the receiver. In other words, if this state could be safely replaced // with the return value of NewState and be functionally equivalent. func (s *State) Empty() bool { + if s == nil { + return true + } for _, ms := range s.Modules { if len(ms.Resources) != 0 { return false @@ -86,6 +89,9 @@ func (s *State) RemoveModule(addr addrs.ModuleInstance) { // RootModule is a convenient alias for Module(addrs.RootModuleInstance). func (s *State) RootModule() *Module { + if s == nil { + panic("RootModule called on nil State") + } return s.Modules[addrs.RootModuleInstance.String()] } diff --git a/states/statefile/marshal_equal.go b/states/statefile/marshal_equal.go index c1d553bd2..4948b39b9 100644 --- a/states/statefile/marshal_equal.go +++ b/states/statefile/marshal_equal.go @@ -17,6 +17,11 @@ func StatesMarshalEqual(a, b *states.State) bool { var aBuf bytes.Buffer var bBuf bytes.Buffer + // nil states are not valid states, and so they can never martial equal. + if a == nil || b == nil { + return false + } + // We write here some temporary files that have no header information // populated, thus ensuring that we're only comparing the state itself // and not any metadata. diff --git a/states/statefile/version4.go b/states/statefile/version4.go index 1b82bf702..ee8b65236 100644 --- a/states/statefile/version4.go +++ b/states/statefile/version4.go @@ -298,6 +298,9 @@ func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics { // read/prepare V4 functions above would stick around. var diags tfdiags.Diagnostics + if file == nil || file.State == nil { + panic("attempt to write nil state to file") + } var terraformVersion string if file.TerraformVersion != nil { diff --git a/states/statemgr/filesystem.go b/states/statemgr/filesystem.go index bbb03dad7..51cc31037 100644 --- a/states/statemgr/filesystem.go +++ b/states/statemgr/filesystem.go @@ -101,6 +101,12 @@ func (s *Filesystem) SetBackupPath(path string) { s.writtenBackup = false } +// BackupPath returns the manager's backup path if backup files are enabled, +// or an empty string otherwise. +func (s *Filesystem) BackupPath() string { + return s.backupPath +} + // State is an implementation of Reader. func (s *Filesystem) State() *states.State { defer s.mutex()()