From 727e22e76287ca1d1c37002e3a40b0ba30c0b356 Mon Sep 17 00:00:00 2001 From: CJ Horton Date: Wed, 15 Feb 2023 19:29:58 -0800 Subject: [PATCH 1/2] add tests for init syntax error handling With the demise of the early config loader, we want to show core version errors first, followed by backend errors, and only then show other errors with the configuration. --- internal/command/init_test.go | 91 +++++++++++++++++++ .../main.tf | 7 ++ .../init-syntax-invalid-no-backend/main.tf | 3 + .../init-syntax-invalid-with-backend/main.tf | 7 ++ 4 files changed, 108 insertions(+) create mode 100644 internal/command/testdata/init-syntax-invalid-backend-invalid/main.tf create mode 100644 internal/command/testdata/init-syntax-invalid-no-backend/main.tf create mode 100644 internal/command/testdata/init-syntax-invalid-with-backend/main.tf diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 2609677bdf..e81db3e205 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -18,6 +18,7 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" @@ -2620,6 +2621,96 @@ func TestInit_invalidBuiltInProviders(t *testing.T) { } } +func TestInit_invalidSyntaxNoBackend(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("init-syntax-invalid-no-backend"), td) + defer testChdir(t, td)() + + ui := cli.NewMockUi() + view, _ := testView(t) + m := Meta{ + Ui: ui, + View: view, + } + + c := &InitCommand{ + Meta: m, + } + + if code := c.Run(nil); code == 0 { + t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter) + } + + errStr := ui.ErrorWriter.String() + if subStr := "There are some problems with the configuration, described below"; !strings.Contains(errStr, subStr) { + t.Errorf("Error output should include preamble\nwant substr: %s\ngot:\n%s", subStr, errStr) + } + if subStr := "Error: Unsupported block type"; !strings.Contains(errStr, subStr) { + t.Errorf("Error output should mention the syntax problem\nwant substr: %s\ngot:\n%s", subStr, errStr) + } +} + +func TestInit_invalidSyntaxWithBackend(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("init-syntax-invalid-with-backend"), td) + defer testChdir(t, td)() + + ui := cli.NewMockUi() + view, _ := testView(t) + m := Meta{ + Ui: ui, + View: view, + } + + c := &InitCommand{ + Meta: m, + } + + if code := c.Run(nil); code == 0 { + t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter) + } + + errStr := ui.ErrorWriter.String() + if subStr := "There are some problems with the configuration, described below"; !strings.Contains(errStr, subStr) { + t.Errorf("Error output should include preamble\nwant substr: %s\ngot:\n%s", subStr, errStr) + } + if subStr := "Error: Unsupported block type"; !strings.Contains(errStr, subStr) { + t.Errorf("Error output should mention the syntax problem\nwant substr: %s\ngot:\n%s", subStr, errStr) + } +} + +func TestInit_invalidSyntaxInvalidBackend(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("init-syntax-invalid-backend-invalid"), td) + defer testChdir(t, td)() + + ui := cli.NewMockUi() + view, _ := testView(t) + m := Meta{ + Ui: ui, + View: view, + } + + c := &InitCommand{ + Meta: m, + } + + if code := c.Run(nil); code == 0 { + t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter) + } + + errStr := ui.ErrorWriter.String() + if subStr := "There are some problems with the configuration, described below"; strings.Contains(errStr, subStr) { + t.Errorf("Error output should not include preamble\nwant substr: %s\ngot:\n%s", subStr, errStr) + } + if subStr := "Error: Unsupported block type"; strings.Contains(errStr, subStr) { + t.Errorf("Error output should not mention syntax errors\nwant substr: %s\ngot:\n%s", subStr, errStr) + } + if subStr := "Error: Unsupported backend type"; !strings.Contains(errStr, subStr) { + t.Errorf("Error output should mention the invalid backend\nwant substr: %s\ngot:\n%s", subStr, errStr) + } +} + // newMockProviderSource is a helper to succinctly construct a mock provider // source that contains a set of packages matching the given provider versions // that are available for installation (from temporary local files). diff --git a/internal/command/testdata/init-syntax-invalid-backend-invalid/main.tf b/internal/command/testdata/init-syntax-invalid-backend-invalid/main.tf new file mode 100644 index 0000000000..4fb3f33692 --- /dev/null +++ b/internal/command/testdata/init-syntax-invalid-backend-invalid/main.tf @@ -0,0 +1,7 @@ +terraform { + backend "nonexistent" {} +} + +bad_block { +} + diff --git a/internal/command/testdata/init-syntax-invalid-no-backend/main.tf b/internal/command/testdata/init-syntax-invalid-no-backend/main.tf new file mode 100644 index 0000000000..5f1451d26c --- /dev/null +++ b/internal/command/testdata/init-syntax-invalid-no-backend/main.tf @@ -0,0 +1,3 @@ +bad_block { +} + diff --git a/internal/command/testdata/init-syntax-invalid-with-backend/main.tf b/internal/command/testdata/init-syntax-invalid-with-backend/main.tf new file mode 100644 index 0000000000..2ea4406cc1 --- /dev/null +++ b/internal/command/testdata/init-syntax-invalid-with-backend/main.tf @@ -0,0 +1,7 @@ +terraform { + backend "local" {} +} + +bad_block { +} + From 30f8b014f8501f4922f69217b8dc69cda7aba964 Mon Sep 17 00:00:00 2001 From: CJ Horton Date: Wed, 15 Feb 2023 20:56:38 -0800 Subject: [PATCH 2/2] keep the friendly error message whenever possible --- internal/command/init.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index ba0d6db934..2e6d13d9da 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -244,14 +244,28 @@ func (c *InitCommand) Run(args []string) int { return 1 } - // If the core version is OK, show any underlying config errors uncovered when initializing - // the backend. - diags = diags.Append(backDiags) - if backDiags.HasErrors() { - c.showDiagnostics(diags) - return 1 + // If we pass the core version check, we want to show any errors from initializing the backend next, + // which will include syntax errors from loading the configuration. However, there's a special case + // where we are unable to load the backend from configuration or state _and_ the configuration has + // errors. In that case, we want to show a slightly friendlier error message for newcomers. + showBackendDiags := back != nil || rootModEarly.Backend != nil || rootModEarly.CloudConfig != nil + if showBackendDiags { + diags = diags.Append(backDiags) + if backDiags.HasErrors() { + c.showDiagnostics(diags) + return 1 + } + } else { + diags = diags.Append(earlyConfDiags) + if earlyConfDiags.HasErrors() { + c.Ui.Error(strings.TrimSpace(errInitConfigError)) + c.showDiagnostics(diags) + return 1 + } } + // If everything is ok with the core version check and backend initialization, + // show other errors from loading the full configuration tree. diags = diags.Append(confDiags) if confDiags.HasErrors() { c.Ui.Error(strings.TrimSpace(errInitConfigError))