Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions internal/command/init2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"path/filepath"
"strings"
"testing"

"github.com/hashicorp/cli"
)

func TestInit2_dynamicSourceErrors(t *testing.T) {
Expand Down Expand Up @@ -800,6 +802,13 @@ func TestInit2_dynamicProviderSourceSuccess(t *testing.T) {
"hashicorp2/test": {"1.0.0"},
},
},
"const with extra resource and provider local name": {
fixture: "provider-source-with-resources-and-provider-local-name",
args: []string{},
providers: map[string][]string{
"hashicorp2/test": {"1.0.0"},
},
},
}

for name, tc := range tests {
Expand Down
4 changes: 2 additions & 2 deletions internal/command/init_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
return 0
}

// Load just the root module to begin backend and module initialization
rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, initArgs.TestsDirectory)
// Load just the raw root module to begin backend and module initialization.
rootModEarly, earlyConfDiags := c.loadRawModuleWithTests(path, initArgs.TestsDirectory)

// There may be parsing errors in config loading but these will be shown later _after_
// checking for core version requirement errors. Not meeting the version requirement should
Expand Down
4 changes: 2 additions & 2 deletions internal/command/meta_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"strings"

"github.com/hashicorp/cli"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
Expand Down Expand Up @@ -2070,7 +2070,7 @@ func (m *Meta) backend(configPath string, viewType arguments.ViewType) (backendr

// Only return error diagnostics at this point. Any warnings will be caught
// again later and duplicated in the output.
root, mDiags := m.loadSingleModule(configPath)
root, mDiags := m.loadRawModule(configPath)
if mDiags.HasErrors() {
diags = diags.Append(mDiags)
return nil, diags
Expand Down
87 changes: 73 additions & 14 deletions internal/command/meta_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (m *Meta) normalizePath(path string) string {
// If no const variables are unsatisfied, or if the backend does not support
// supplying variables, this method is a no-op.
func (m *Meta) resolveConstVariables(rootDir string, viewType arguments.ViewType) tfdiags.Diagnostics {
rootMod, diags := m.loadSingleModule(rootDir)
rootMod, diags := m.loadRawModule(rootDir)
if diags.HasErrors() {
return diags
}
Expand Down Expand Up @@ -168,15 +168,8 @@ func (m *Meta) loadConfigWithTests(rootDir, testDir string) (*configs.Config, tf
return config, diags
}

// loadSingleModule reads configuration from the given directory and returns
// a description of that module only, without attempting to assemble a module
// tree for referenced child modules.
//
// Most callers should use loadConfig. This method exists to support early
// initialization use-cases where the root module must be inspected in order
// to determine what else needs to be installed before the full configuration
// can be used.
func (m *Meta) loadSingleModule(dir string) (*configs.Module, tfdiags.Diagnostics) {
// Load module without running init graph
func (m *Meta) loadRawModule(dir string) (*configs.Module, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
dir = m.normalizePath(dir)

Expand All @@ -191,9 +184,7 @@ func (m *Meta) loadSingleModule(dir string) (*configs.Module, tfdiags.Diagnostic
return module, diags
}

// loadSingleModuleWithTests matches loadSingleModule except it also loads any
// tests for the target module.
func (m *Meta) loadSingleModuleWithTests(dir string, testDir string) (*configs.Module, tfdiags.Diagnostics) {
func (m *Meta) loadRawModuleWithTests(dir string, testDir string) (*configs.Module, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
dir = m.normalizePath(dir)

Expand All @@ -208,6 +199,72 @@ func (m *Meta) loadSingleModuleWithTests(dir string, testDir string) (*configs.M
return module, diags
}

// loadSingleModule reads configuration from the given directory and returns
// a description of that module only, without attempting to assemble a module
// tree for referenced child modules. It runs the init graph to resolve any
// dynamic provider/module source expressions using the caller's const variable
// values.
//
// Most callers should use loadConfig. This method exists to support early
// initialization use-cases where the root module must be inspected in order
// to determine what else needs to be installed before the full configuration
// can be used.
func (m *Meta) loadSingleModule(dir string) (*configs.Module, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
dir = m.normalizePath(dir)

loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
return nil, diags
}

module, hclDiags := loader.Parser().LoadConfigDir(dir)
diags = diags.Append(hclDiags)
if diags.HasErrors() {
return nil, diags
}

vars, varDiags := backendrun.ParseConstVariableValues(m.VariableValues, module.Variables)
diags = diags.Append(varDiags)
if varDiags.HasErrors() {
return nil, diags
}

mod, buildDiags := terraform.BuildModuleWithGraph(module, vars)
diags = diags.Append(buildDiags)
return mod, diags
}

// loadSingleModuleWithTests matches loadSingleModule except it also loads any
// tests for the target module.
//func (m *Meta) loadSingleModuleWithTests(dir string, testDir string) (*configs.Module, tfdiags.Diagnostics) {
// var diags tfdiags.Diagnostics
// dir = m.normalizePath(dir)
//
// loader, err := m.initConfigLoader()
// if err != nil {
// diags = diags.Append(err)
// return nil, diags
// }
//
// module, hclDiags := loader.Parser().LoadConfigDirWithTests(dir, testDir)
// diags = diags.Append(hclDiags)
// if diags.HasErrors() {
// return nil, diags
// }
//
// vars, varDiags := backendrun.ParseConstVariableValues(m.VariableValues, module.Variables)
// diags = diags.Append(varDiags)
// if varDiags.HasErrors() {
// return nil, diags
// }
//
// mod, buildDiags := terraform.BuildModuleWithGraph(module, vars)
// diags = diags.Append(buildDiags)
// return mod, diags
//}

// dirIsConfigPath checks if the given path is a directory that contains at
// least one Terraform configuration file (.tf or .tf.json), returning true
// if so.
Expand Down Expand Up @@ -240,7 +297,9 @@ func (m *Meta) dirIsConfigPath(dir string) bool {
// that a call to loadSingleModule or loadConfig could fail on the same
// directory even if loadBackendConfig succeeded.)
func (m *Meta) loadBackendConfig(rootDir string) (*configs.Backend, tfdiags.Diagnostics) {
mod, diags := m.loadSingleModule(rootDir)
// Use loadRawModule here (no init graph) because we only need the
// Backend and CloudConfig fields which are populated by HCL parsing.
mod, diags := m.loadRawModule(rootDir)

// Only return error diagnostics at this point. Any warnings will be caught
// again later and duplicated in the output.
Expand Down
4 changes: 2 additions & 2 deletions internal/command/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ func (c *ModulesCommand) Run(rawArgs []string) int {
return 1
}

// Read the root module path so we can then traverse the tree
rootModEarly, earlyConfDiags := c.loadSingleModule(rootModPath)
// Read the root module path so we can then traverse the tree.
rootModEarly, earlyConfDiags := c.loadRawModule(rootModPath)
if rootModEarly == nil {
diags = diags.Append(errors.New("root module not found. Please run terraform init"), earlyConfDiags)
view.Diagnostics(diags)
Expand Down
9 changes: 6 additions & 3 deletions internal/command/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,12 @@ func (m *Meta) setupTestExecution(mode moduletest.CommandMode, command string, r
// test runs rather than the root module.
//
// We do an early load of just the root module to discover which
// variables are const. We discard non-error diagnostics from this
// early load since loadConfigWithTests will re-parse and report them.
earlyMod, earlyDiags := m.loadSingleModuleWithTests(".", preparation.Args.TestDirectory)
// variables are const. We only need the Variables declarations here,
// so we use loadRawModuleWithTests (no init graph) to avoid a
// chicken-and-egg problem where const variable values aren't known yet.
// Non-error diagnostics are discarded since loadConfigWithTests will
// reparse and report them.
earlyMod, earlyDiags := m.loadRawModuleWithTests(".", preparation.Args.TestDirectory)
if earlyDiags.HasErrors() {
diags = diags.Append(earlyDiags)
view.Diagnostics(nil, nil, diags)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
terraform {
required_providers {
test-local-name = {
source = "${var.namespace}/test"
}
}
}

variable "namespace" {
type = string
const = true
default = "hashicorp2"
}

resource "test_instance" "example" {
provider = test-local-name
}
Loading