command/init: Use full config for provider reqs
Relying on the early config for provider requirements was necessary in Terraform 0.12, to allow the 0.12upgrade command to run after init installs providers. However in 0.13, the same restrictions do not apply, and the detection of provider requirements has changed. As a result, the early config loader gives incorrect provider requirements in some circumstances, such as those in the new test in this commit. Therefore we are changing the init command to use the requirements found by the full configuration loader. This also means that we can remove the internal initwd CheckCoreVersionRequirements function.
This commit is contained in:
parent
df39e0a806
commit
62d826e066
|
@ -18,11 +18,10 @@ import (
|
||||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
|
||||||
"github.com/hashicorp/terraform/internal/getproviders"
|
"github.com/hashicorp/terraform/internal/getproviders"
|
||||||
"github.com/hashicorp/terraform/internal/initwd"
|
|
||||||
"github.com/hashicorp/terraform/internal/providercache"
|
"github.com/hashicorp/terraform/internal/providercache"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
tfversion "github.com/hashicorp/terraform/version"
|
tfversion "github.com/hashicorp/terraform/version"
|
||||||
)
|
)
|
||||||
|
@ -199,21 +198,7 @@ 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)
|
||||||
// Just as above, we'll try loading both with the early and normal config
|
|
||||||
// loaders here. Subsequent work will only use the early config, but loading
|
|
||||||
// both gives us an opportunity to prefer the better error messages from the
|
|
||||||
// normal loader if both fail.
|
|
||||||
|
|
||||||
earlyConfig, earlyConfDiags := c.loadConfigEarly(path)
|
|
||||||
if earlyConfDiags.HasErrors() {
|
|
||||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
|
||||||
diags = diags.Append(earlyConfDiags)
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_, confDiags = c.loadConfig(path)
|
|
||||||
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))
|
||||||
|
@ -225,7 +210,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
// configuration declare that they don't support this Terraform version, so
|
// configuration declare that they don't support this Terraform version, so
|
||||||
// we can produce a version-related error message rather than
|
// we can produce a version-related error message rather than
|
||||||
// potentially-confusing downstream errors.
|
// potentially-confusing downstream errors.
|
||||||
versionDiags := initwd.CheckCoreVersionRequirements(earlyConfig)
|
versionDiags := terraform.CheckCoreVersionRequirements(config)
|
||||||
diags = diags.Append(versionDiags)
|
diags = diags.Append(versionDiags)
|
||||||
if versionDiags.HasErrors() {
|
if versionDiags.HasErrors() {
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
|
@ -294,7 +279,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have loaded all modules, check the module tree for missing providers.
|
// Now that we have loaded all modules, check the module tree for missing providers.
|
||||||
providersOutput, providerDiags := c.getProviders(earlyConfig, state, flagUpgrade, flagPluginPath)
|
providersOutput, providerDiags := c.getProviders(config, state, flagUpgrade, flagPluginPath)
|
||||||
diags = diags.Append(providerDiags)
|
diags = diags.Append(providerDiags)
|
||||||
if providerDiags.HasErrors() {
|
if providerDiags.HasErrors() {
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
|
@ -425,10 +410,10 @@ the backend configuration is present and valid.
|
||||||
|
|
||||||
// Load the complete module tree, and fetch any missing providers.
|
// Load the complete module tree, and fetch any missing providers.
|
||||||
// This method outputs its own Ui.
|
// This method outputs its own Ui.
|
||||||
func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *states.State, upgrade bool, pluginDirs []string) (output bool, diags tfdiags.Diagnostics) {
|
func (c *InitCommand) getProviders(config *configs.Config, state *states.State, upgrade bool, pluginDirs []string) (output bool, diags tfdiags.Diagnostics) {
|
||||||
// First we'll collect all the provider dependencies we can see in the
|
// First we'll collect all the provider dependencies we can see in the
|
||||||
// configuration and the state.
|
// configuration and the state.
|
||||||
reqs, moreDiags := earlyConfig.ProviderRequirements()
|
reqs, moreDiags := config.ProviderRequirements()
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
if moreDiags.HasErrors() {
|
if moreDiags.HasErrors() {
|
||||||
return false, diags
|
return false, diags
|
||||||
|
|
|
@ -849,6 +849,55 @@ func TestInit_getProvider(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInit_getProviderSource(t *testing.T) {
|
||||||
|
// Create a temporary working directory that is empty
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("init-get-provider-source"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
overrides := metaOverridesForProvider(testProvider())
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||||
|
// looking for an exact version
|
||||||
|
"acme/alpha": []string{"1.2.3"},
|
||||||
|
// config doesn't specify versions for other providers
|
||||||
|
"registry.example.com/acme/beta": []string{"1.0.0"},
|
||||||
|
"gamma": []string{"2.0.0"},
|
||||||
|
})
|
||||||
|
defer close()
|
||||||
|
m := Meta{
|
||||||
|
testingOverrides: overrides,
|
||||||
|
Ui: ui,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &InitCommand{
|
||||||
|
Meta: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-backend=false", // should be possible to install plugins without backend init
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that we got the providers for our config
|
||||||
|
exactPath := fmt.Sprintf(".terraform/plugins/registry.terraform.io/acme/alpha/1.2.3/%s", getproviders.CurrentPlatform)
|
||||||
|
if _, err := os.Stat(exactPath); os.IsNotExist(err) {
|
||||||
|
t.Fatal("provider 'alpha' not downloaded")
|
||||||
|
}
|
||||||
|
greaterThanPath := fmt.Sprintf(".terraform/plugins/registry.example.com/acme/beta/1.0.0/%s", getproviders.CurrentPlatform)
|
||||||
|
if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) {
|
||||||
|
t.Fatal("provider 'beta' not downloaded")
|
||||||
|
}
|
||||||
|
betweenPath := fmt.Sprintf(".terraform/plugins/registry.terraform.io/hashicorp/gamma/2.0.0/%s", getproviders.CurrentPlatform)
|
||||||
|
if _, err := os.Stat(betweenPath); os.IsNotExist(err) {
|
||||||
|
t.Fatal("provider 'gamma' not downloaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInit_providerSource(t *testing.T) {
|
func TestInit_providerSource(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)
|
||||||
|
@ -1526,10 +1575,10 @@ func TestInit_syntaxErrorUpgradeHint(t *testing.T) {
|
||||||
// longer needed, at which point it will clean up all of the temporary files
|
// longer needed, at which point it will clean up all of the temporary files
|
||||||
// and the packages in the source will no longer be available for installation.
|
// and the packages in the source will no longer be available for installation.
|
||||||
//
|
//
|
||||||
// For ease of use in the common case, this function just treats all of the
|
// Provider addresses must be valid source strings, and passing only the
|
||||||
// provider given names as "default" providers under
|
// provider name will be interpreted as a "default" provider under
|
||||||
// registry.terraform.io/hashicorp . If you need more control over the
|
// registry.terraform.io/hashicorp. If you need more control over the
|
||||||
// provider addresses, construct a getproviders.MockSource directly instead.
|
// provider addresses, pass a full provider source string.
|
||||||
//
|
//
|
||||||
// This function also registers providers as belonging to the current platform,
|
// This function also registers providers as belonging to the current platform,
|
||||||
// to ensure that they will be available to a provider installer operating in
|
// to ensure that they will be available to a provider installer operating in
|
||||||
|
@ -1548,18 +1597,18 @@ func newMockProviderSource(t *testing.T, availableProviderVersions map[string][]
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for name, versions := range availableProviderVersions {
|
for source, versions := range availableProviderVersions {
|
||||||
addr := addrs.NewDefaultProvider(name)
|
addr := addrs.MustParseProviderSourceString(source)
|
||||||
for _, versionStr := range versions {
|
for _, versionStr := range versions {
|
||||||
version, err := getproviders.ParseVersion(versionStr)
|
version, err := getproviders.ParseVersion(versionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close()
|
close()
|
||||||
t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, name, err)
|
t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, addr.ForDisplay(), err)
|
||||||
}
|
}
|
||||||
meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform)
|
meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close()
|
close()
|
||||||
t.Fatalf("failed to prepare fake package for %s %s: %s", name, versionStr, err)
|
t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), versionStr, err)
|
||||||
}
|
}
|
||||||
closes = append(closes, close)
|
closes = append(closes, close)
|
||||||
packages = append(packages, meta)
|
packages = append(packages, meta)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
provider alpha {
|
||||||
|
version = "1.2.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource beta_resource b {}
|
||||||
|
resource gamma_resource g {}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
alpha = {
|
||||||
|
source = "acme/alpha"
|
||||||
|
}
|
||||||
|
beta = {
|
||||||
|
source = "registry.example.com/acme/beta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider beta {
|
||||||
|
region = "foo"
|
||||||
|
}
|
|
@ -1,83 +0,0 @@
|
||||||
package initwd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
|
||||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
tfversion "github.com/hashicorp/terraform/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckCoreVersionRequirements visits each of the modules in the given
|
|
||||||
// early configuration tree and verifies that any given Core version constraints
|
|
||||||
// match with the version of Terraform Core that is being used.
|
|
||||||
//
|
|
||||||
// The returned diagnostics will contain errors if any constraints do not match.
|
|
||||||
// The returned diagnostics might also return warnings, which should be
|
|
||||||
// displayed to the user.
|
|
||||||
func CheckCoreVersionRequirements(earlyConfig *earlyconfig.Config) tfdiags.Diagnostics {
|
|
||||||
if earlyConfig == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
module := earlyConfig.Module
|
|
||||||
|
|
||||||
var constraints version.Constraints
|
|
||||||
for _, constraintStr := range module.RequiredCore {
|
|
||||||
constraint, err := version.NewConstraint(constraintStr)
|
|
||||||
if err != nil {
|
|
||||||
// Unfortunately the early config parser doesn't preserve a source
|
|
||||||
// location for this, so we're unable to indicate a specific
|
|
||||||
// location where this constraint came from, but we can at least
|
|
||||||
// say which module set it.
|
|
||||||
switch {
|
|
||||||
case len(earlyConfig.Path) == 0:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Invalid provider version constraint",
|
|
||||||
fmt.Sprintf("Invalid version core constraint %q in the root module.", constraintStr),
|
|
||||||
))
|
|
||||||
default:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Invalid provider version constraint",
|
|
||||||
fmt.Sprintf("Invalid version core constraint %q in %s.", constraintStr, earlyConfig.Path),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
constraints = append(constraints, constraint...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !constraints.Check(tfversion.SemVer) {
|
|
||||||
switch {
|
|
||||||
case len(earlyConfig.Path) == 0:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Unsupported Terraform Core version",
|
|
||||||
fmt.Sprintf(
|
|
||||||
"This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update the root module's version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
|
||||||
tfversion.String(),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
default:
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Unsupported Terraform Core version",
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Module %s (from %q) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update the module's version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
|
||||||
earlyConfig.Path, earlyConfig.SourceAddr, tfversion.String(),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range earlyConfig.Children {
|
|
||||||
childDiags := CheckCoreVersionRequirements(c)
|
|
||||||
diags = diags.Append(childDiags)
|
|
||||||
}
|
|
||||||
|
|
||||||
return diags
|
|
||||||
}
|
|
Loading…
Reference in New Issue