Merge pull request #29665 from hashicorp/jbardin/required_version
Check required_version as early as possible
This commit is contained in:
commit
f93f16824c
|
@ -150,19 +150,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
// initialization functionality remains built around "earlyconfig" and
|
// initialization functionality remains built around "earlyconfig" and
|
||||||
// so we need to still load the module via that mechanism anyway until we
|
// so we need to still load the module via that mechanism anyway until we
|
||||||
// can do some more invasive refactoring here.
|
// can do some more invasive refactoring here.
|
||||||
rootMod, confDiags := c.loadSingleModule(path)
|
|
||||||
rootModEarly, earlyConfDiags := c.loadSingleModuleEarly(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
|
// If _only_ the early loader encountered errors then that's unusual
|
||||||
// (it should generally be a superset of the normal loader) but we'll
|
// (it should generally be a superset of the normal loader) but we'll
|
||||||
// return those errors anyway since otherwise we'll probably get
|
// 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)))
|
c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError)))
|
||||||
// Errors from the early loader are generally not as high-quality since
|
// Errors from the early loader are generally not as high-quality since
|
||||||
// it has less context to work with.
|
// 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)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -192,6 +185,20 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
// With all of the modules (hopefully) installed, we can now try to load the
|
// With all of the modules (hopefully) installed, we can now try to load the
|
||||||
// whole configuration tree.
|
// whole configuration tree.
|
||||||
config, confDiags := c.loadConfig(path)
|
config, confDiags := c.loadConfig(path)
|
||||||
|
// 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
|
||||||
|
// potentially-confusing downstream errors.
|
||||||
|
versionDiags := terraform.CheckCoreVersionRequirements(config)
|
||||||
|
if versionDiags.HasErrors() {
|
||||||
|
c.showDiagnostics(versionDiags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
diags = diags.Append(confDiags)
|
diags = diags.Append(confDiags)
|
||||||
if confDiags.HasErrors() {
|
if confDiags.HasErrors() {
|
||||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||||
|
@ -199,21 +206,10 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
// potentially-confusing downstream errors.
|
|
||||||
versionDiags := terraform.CheckCoreVersionRequirements(config)
|
|
||||||
diags = diags.Append(versionDiags)
|
|
||||||
if versionDiags.HasErrors() {
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var back backend.Backend
|
var back backend.Backend
|
||||||
if flagBackend {
|
if flagBackend {
|
||||||
|
|
||||||
be, backendOutput, backendDiags := c.initBackend(rootMod, flagConfigExtra)
|
be, backendOutput, backendDiags := c.initBackend(config.Module, flagConfigExtra)
|
||||||
diags = diags.Append(backendDiags)
|
diags = diags.Append(backendDiags)
|
||||||
if backendDiags.HasErrors() {
|
if backendDiags.HasErrors() {
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
|
|
|
@ -1608,6 +1608,59 @@ 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) {
|
||||||
|
t.Run("root_module", func(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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("sub_module", func(t *testing.T) {
|
||||||
|
td := t.TempDir()
|
||||||
|
testCopyDir(t, testFixturePath("init-check-required-version-first-module"), 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) {
|
func TestInit_providerLockFile(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
|
|
|
@ -103,8 +103,6 @@ func TestShow_aliasedProvider(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(os.Getwd())
|
|
||||||
|
|
||||||
// the statefile created by testStateFile is named state.tfstate
|
// the statefile created by testStateFile is named state.tfstate
|
||||||
args := []string{"state.tfstate"}
|
args := []string{"state.tfstate"}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
module "mod" {
|
||||||
|
source = "./mod"
|
||||||
|
}
|
17
internal/command/testdata/init-check-required-version-first-module/mod/main.tf
vendored
Normal file
17
internal/command/testdata/init-check-required-version-first-module/mod/main.tf
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
terraform {
|
||||||
|
required_version = ">200.0.0"
|
||||||
|
|
||||||
|
bad {
|
||||||
|
block = "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
bang = {
|
||||||
|
oops = "boom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nope {
|
||||||
|
boom {}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
terraform {
|
||||||
|
required_version = ">200.0.0"
|
||||||
|
|
||||||
|
bad {
|
||||||
|
block = "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
bang = {
|
||||||
|
oops = "boom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nope {
|
||||||
|
boom {}
|
||||||
|
}
|
|
@ -21,7 +21,13 @@ import (
|
||||||
func (l *Loader) LoadConfig(rootDir string) (*configs.Config, hcl.Diagnostics) {
|
func (l *Loader) LoadConfig(rootDir string) (*configs.Config, hcl.Diagnostics) {
|
||||||
rootMod, diags := l.parser.LoadConfigDir(rootDir)
|
rootMod, diags := l.parser.LoadConfigDir(rootDir)
|
||||||
if rootMod == nil || diags.HasErrors() {
|
if rootMod == nil || diags.HasErrors() {
|
||||||
return nil, diags
|
// Ensure we return any parsed modules here so that required_version
|
||||||
|
// constraints can be verified even when encountering errors.
|
||||||
|
cfg := &configs.Config{
|
||||||
|
Module: rootMod,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, cDiags := configs.BuildConfig(rootMod, configs.ModuleWalkerFunc(l.moduleWalkerLoad))
|
cfg, cDiags := configs.BuildConfig(rootMod, configs.ModuleWalkerFunc(l.moduleWalkerLoad))
|
||||||
|
|
|
@ -91,8 +91,16 @@ func TestLoaderLoadConfig_loadDiags(t *testing.T) {
|
||||||
t.Fatalf("unexpected error from NewLoader: %s", err)
|
t.Fatalf("unexpected error from NewLoader: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, diags := loader.LoadConfig(fixtureDir)
|
cfg, diags := loader.LoadConfig(fixtureDir)
|
||||||
if !diags.HasErrors() {
|
if !diags.HasErrors() {
|
||||||
t.Fatalf("success; want error")
|
t.Fatal("success; want error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == nil {
|
||||||
|
t.Fatal("partial config not returned with diagnostics")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Module == nil {
|
||||||
|
t.Fatal("expected config module")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue