Open-Source Wikis

/

Terraform

/

Systems

/

Configuration loading

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 configurations

Key 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:

  1. Late-bound bodies. A configuration like provider "aws" { region = var.region } cannot be fully decoded at parse time, because var.region needs the variable values that haven't been provided yet. Configs keeps these as hcl.Body/hcl.Expression and lets the engine evaluate them later via internal/lang.
  2. Module installation. Child modules can be local paths, registry references, or remote sources (Git, HTTP, S3, …). Installation is delegated to internal/initwd (which uses internal/getmodules); configload.Loader stitches 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/ — defines configschema.Block and Attribute, 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 quick configs.Module from 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.loadConfig and command.Meta.loadSingleModule are the typical entrypoints; they wrap a configload.Loader.
  • Engine: terraform.Context accepts a *configs.Config directly. Most engine packages do not depend on configs for evaluation — they rely on the typed model already being constructed.
  • Test runner: internal/moduletest/ consumes configs.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 {} or state_store {}): create a new file alongside resource.go, expose a typed model, plumb it through configs.Module, parse it from parser_config.go, and validate it where appropriate.
  • Adding a new attribute to an existing block: edit the schema in the relevant *.go file and update module_merge.go if the attribute participates in override merging.
  • Changing how modules are installed: most logic lives in internal/initwd/, not here. configs.Loader only 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.

Configuration loading – Terraform wiki | Factory