diff --git a/internal/command/init.go b/internal/command/init.go index 96a383159f04..02f555ee0a8e 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -150,19 +150,7 @@ func (c *InitCommand) Run(args []string) int { // initialization functionality remains built around "earlyconfig" and // so we need to still load the module via that mechanism anyway until we // can do some more invasive refactoring here. - rootMod, confDiags := c.loadSingleModule(path) rootModEarly, earlyConfDiags := c.loadSingleModuleEarly(path) - if confDiags.HasErrors() { - c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError))) - // TODO: It would be nice to check the version constraints in - // rootModEarly.RequiredCore and print out a hint if the module is - // declaring that it's not compatible with this version of Terraform, - // though we're deferring that for now because we're intending to - // refactor our use of "earlyconfig" here anyway and so whatever we - // might do here right now would likely be invalidated by that. - c.showDiagnostics(confDiags) - return 1 - } // If _only_ the early loader encountered errors then that's unusual // (it should generally be a superset of the normal loader) but we'll // return those errors anyway since otherwise we'll probably get @@ -172,7 +160,12 @@ func (c *InitCommand) Run(args []string) int { c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError))) // Errors from the early loader are generally not as high-quality since // it has less context to work with. - diags = diags.Append(confDiags) + + // TODO: It would be nice to check the version constraints in + // rootModEarly.RequiredCore and print out a hint if the module is + // declaring that it's not compatible with this version of Terraform, + // and that may be what caused earlyconfig to fail. + diags = diags.Append(earlyConfDiags) c.showDiagnostics(diags) return 1 } @@ -189,23 +182,44 @@ func (c *InitCommand) Run(args []string) int { } } + // Using loadSingleModule will allow us to get the sniffed required_version + // before trying to build the complete config. + rootMod, _ := c.loadSingleModule(path) + // We can ignore the error, since we are going to reload the full config + // again below once we know the root module constraints are valid. + if rootMod != nil { + rootCfg := &configs.Config{ + Module: rootMod, + } + // If our module version constraints are not valid, then there is no + // need to continue processing. + versionDiags := terraform.CheckCoreVersionRequirements(rootCfg) + if versionDiags.HasErrors() { + c.showDiagnostics(versionDiags) + return 1 + } + } + // With all of the modules (hopefully) installed, we can now try to load the // whole configuration tree. config, confDiags := c.loadConfig(path) - diags = diags.Append(confDiags) - if confDiags.HasErrors() { - c.Ui.Error(strings.TrimSpace(errInitConfigError)) - c.showDiagnostics(diags) - return 1 - } + // configDiags will be handled after the version constraint check, since an + // incorrect version of terraform may be producing errors for configuration + // constructs added in later versions. - // Before we go further, we'll check to make sure none of the modules in the - // configuration declare that they don't support this Terraform version, so - // we can produce a version-related error message rather than + // Check again to make sure none of the modules in the configuration + // declare that they don't support this Terraform version, so we can + // produce a version-related error message rather than // potentially-confusing downstream errors. versionDiags := terraform.CheckCoreVersionRequirements(config) - diags = diags.Append(versionDiags) if versionDiags.HasErrors() { + c.showDiagnostics(versionDiags) + return 1 + } + + diags = diags.Append(confDiags) + if confDiags.HasErrors() { + c.Ui.Error(strings.TrimSpace(errInitConfigError)) c.showDiagnostics(diags) return 1 } @@ -213,7 +227,7 @@ func (c *InitCommand) Run(args []string) int { var back backend.Backend if flagBackend { - be, backendOutput, backendDiags := c.initBackend(rootMod, flagConfigExtra) + be, backendOutput, backendDiags := c.initBackend(config.Root.Module, flagConfigExtra) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index b75da1f4f899..2bf1e0e26481 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -1608,6 +1608,33 @@ func TestInit_checkRequiredVersion(t *testing.T) { } } +// Verify that init will error out with an invalid version constraint, even if +// there are other invalid configuration constructs. +func TestInit_checkRequiredVersionFirst(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("init-check-required-version-first"), td) + defer testChdir(t, td)() + + ui := cli.NewMockUi() + view, _ := testView(t) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + View: view, + }, + } + + args := []string{} + if code := c.Run(args); code != 1 { + t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) + } + errStr := ui.ErrorWriter.String() + if !strings.Contains(errStr, `Unsupported Terraform Core version`) { + t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) + } +} + func TestInit_providerLockFile(t *testing.T) { // Create a temporary working directory that is empty td := tempDir(t) diff --git a/internal/command/testdata/init-check-required-version-first/main.tf b/internal/command/testdata/init-check-required-version-first/main.tf new file mode 100644 index 000000000000..ab311d066953 --- /dev/null +++ b/internal/command/testdata/init-check-required-version-first/main.tf @@ -0,0 +1,17 @@ +terraform { + required_version = ">200.0.0" + + bad { + block = "false" + } + + required_providers { + bang = { + oops = "boom" + } + } +} + +nope { + boom {} +}