hashicorp/terraform
Configuration loading
Active contributors: Martin Atkins, James Bardin, Liam Cervante.
Purpose
internal/configs/ translates a directory of .tf and .tf.json files into a typed Go model rooted at configs.Config. It is the layer between raw HCL and the rest of the codebase. It also parses .tftest.hcl files for the native test runner, mock-provider blocks, query files, and the new state_store {}, action {}, and removed {} extensions.
Directory layout
internal/configs/
├── parser.go, parser_config.go, parser_config_dir.go
├── parser_file_matcher.go # decides which files in a directory belong to a config
├── module.go # configs.Module (one directory's worth of config)
├── module_call.go # `module "x" { source = "..." }` call sites
├── module_merge.go # override and merge logic
├── named_values.go # variable, output, local
├── provider.go # provider blocks
├── provider_requirements.go # required_providers
├── provider_validation.go # validates providers / config
├── resource.go # resource and data blocks
├── module.go, config.go # multi-module assembly (config_build.go does the assembly)
├── config_build.go # builds *configs.Config from *configs.Module trees
├── moved.go, removed.go # refactoring blocks
├── action.go # action {} blocks (1.16+)
├── state_store.go # provider-implemented state storage
├── test_file.go # .tftest.hcl files (40 KB)
├── mock_provider.go # mock_provider {} for tests
├── query_file.go # .tfquery.hcl files
├── experiments.go # experiment opt-ins per module
├── version_constraint.go # version constraints used throughout
├── doc.go
├── configload/ # high-level Loader + module installer integration
├── configschema/ # block/attribute schemas (provider-independent layer)
├── configtesting/ # test helpers shared with other packages
├── hcl2shim/ # converters between cty values and other shapes
└── testdata/ # 25 sub-directories of fixture configurationsKey types
| Type | File | Description |
|---|---|---|
configs.Parser |
parser.go |
Wraps an hclparse.Parser, an afero.Fs, and a parser_file_matcher. Single point of HCL parsing. |
configs.Module |
module.go |
One directory of .tf files reduced to typed fields: variables, outputs, locals, providers, resources, data sources, module calls, moved/removed/import blocks. |
configs.Config |
config.go |
A tree of modules. The root is the configuration the user pointed Terraform at; children come from module blocks. |
configs.Resource |
resource.go |
A resource or data block. Mode distinguishes them. |
configs.ModuleCall |
module_call.go |
A module "x" { source = "..." } invocation. |
configs.Variable, Output, Local |
named_values.go |
The three scalar value declarations. |
configs.RequiredProvider |
provider_requirements.go |
One entry from required_providers. |
configs.Backend, Cloud |
backend.go, cloud.go |
terraform { backend "..." {} } / cloud {} blocks. |
configs.TestFile, Run, Runner |
test_file.go |
The native test framework's parser model. |
configs.MockProvider |
mock_provider.go |
mock_provider {} blocks used inside test files. |
configs.Action |
action.go |
action {} blocks for the new actions feature. |
configs.MovedBlock, RemovedBlock, Import |
moved.go, removed.go, import.go |
Refactoring/import blocks. |
How it works
graph TD
User["./<root module dir>"] --> Loader[configload.Loader]
Loader --> Inst[initwd installer]
Inst --> Cache[.terraform/modules]
Loader --> Parser[configs.Parser]
Parser --> HCL[hashicorp/hcl/v2]
HCL --> File[*hcl.File]
File --> Module[parser.LoadConfigDir → configs.Module]
Module --> Build[config_build.go: BuildConfig]
Build --> Children[recursively load child modules]
Build --> Tree[configs.Config tree]
Tree --> Validate[validateProviderConfigs / version_required.go]
Validate --> Out[*configs.Config]Two things make this more complicated than a typical HCL parser:
- Late-bound bodies. A configuration like
provider "aws" { region = var.region }cannot be fully decoded at parse time, becausevar.regionneeds the variable values that haven't been provided yet. Configs keeps these ashcl.Body/hcl.Expressionand lets the engine evaluate them later viainternal/lang. - Module installation. Child modules can be local paths, registry references, or remote sources (Git, HTTP, S3, …). Installation is delegated to
internal/initwd(which usesinternal/getmodules);configload.Loaderstitches together the resulting on-disk module tree.
configload.Loader
internal/configs/configload/loader.go is the public entry point. CLI commands like init and plan create one and call LoadConfig (for init, after installing modules; for others, against the on-disk cache from a prior init).
Module installation
internal/initwd/ orchestrates module installation. It walks the configuration to find module blocks, resolves their sources, downloads them via internal/getmodules (which uses hashicorp/go-getter under the hood), writes a manifest in .terraform/modules/modules.json, and re-loads the configuration to verify the resulting tree.
For child modules, the loader trusts the manifest: LoadConfig doesn't do network IO, only the on-disk cache.
File matchers
parser_file_matcher.go decides which files in a directory contribute to the configuration. The default matcher accepts .tf and .tf.json and not their _override.* siblings (which feed into the override pass), and skips files starting with . or _. Override files trigger the merge logic in module_merge.go.
Validation
provider_validation.go does the heavy lifting: every reference to a provider needs a matching entry in some required_providers, every provider "..." {} block needs to be addressed by a known short name, and so on. It produces tfdiags.Diagnostics, not panics; the engine consumes the resulting *configs.Config and can trust that the provider topology is internally consistent.
Sub-packages worth their own pages
internal/configs/configschema/— definesconfigschema.BlockandAttribute, the schema language used everywhere a provider or backend talks about its accepted shape. This is the layer that lets the parser do anything generic with provider configuration; the actual schemas come from the providers themselves at runtime.internal/configs/hcl2shim/— adapters between Terraform's typed configuration and looser representations needed at boundaries (gRPC wire format, JSON state).internal/configs/configtesting/— shared helpers for tests in other packages that need a quickconfigs.Modulefrom inline source.
Test files (.tftest.hcl)
test_file.go (40 KB) parses .tftest.hcl files into a configs.TestFile containing one or more Run blocks. Each run block can command = "plan" or command = "apply", set variables, override providers with mocks, and assert via assert {} blocks. The corresponding runtime lives in internal/moduletest/; see moduletest.
Integration points
- CLI:
command.Meta.loadConfigandcommand.Meta.loadSingleModuleare the typical entrypoints; they wrap aconfigload.Loader. - Engine:
terraform.Contextaccepts a*configs.Configdirectly. Most engine packages do not depend onconfigsfor evaluation — they rely on the typed model already being constructed. - Test runner:
internal/moduletest/consumesconfigs.TestFile. - Stacks:
internal/stacks/stackconfig/is a parallel parser for stack configurations and does not go through this package.
Entry points for modification
- Adding a new top-level block (like
action {}orstate_store {}): create a new file alongsideresource.go, expose a typed model, plumb it throughconfigs.Module, parse it fromparser_config.go, and validate it where appropriate. - Adding a new attribute to an existing block: edit the schema in the relevant
*.gofile and updatemodule_merge.goif the attribute participates in override merging. - Changing how modules are installed: most logic lives in
internal/initwd/, not here.configs.Loaderonly cares about the on-disk layout.
For how the resulting *configs.Config is evaluated, see language evaluation. For how it becomes a graph, see terraform-core.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.