July 29, 2024·6 min read·engineeringterraformopentofumodulestesting

OpenTofu 1.8 evaluates configuration before initialization needs it

OpenTofu 1.8 allows variables and locals in module sources and backend settings. A separate early evaluation stage preserves the requirement that these values exist before ordinary resource planning.

A module source has to be known before the module can be downloaded. A backend destination has to be known before its state can be read. Those ordering requirements explain why apparently ordinary configuration fields have historically resisted the variables used elsewhere in infrastructure definitions. OpenTofu 1.8 permits variables and locals in these positions by evaluating a restricted set of expressions early.

The 1.8.0 release, published on 29 July, includes early evaluation, provider mocking and resource overrides for tests. It also introduces OpenTofu-specific configuration extensions. Together, these changes let module authors express more behavior in the tool itself while maintaining a way to distribute configuration for both OpenTofu and Terraform.

I favor reusable infrastructure definitions because a shared language can make API operations consistent across services. Requiring a separate preprocessing step for a simple backend parameter weakens that model. This release addresses the underlying evaluation order, which is more useful than adding an isolated exception for one configuration field.

Initialization has its own information requirements

The static evaluation RFC shipped in the release describes two stages in the existing implementation. Configuration loading parses blocks and attributes, evaluating some immediately while retaining other expressions for the dependency graph. Later graph evaluation waits for referenced objects and evaluates expressions when their dependencies are available.

A module's source belongs to the first stage. The loader cannot discover the complete configuration tree without installing child modules. Resource-dependent expressions belong to the later stage, after the tree exists and Terraform-style dependency processing can begin. Letting every arbitrary resource value influence module installation would create dependencies across that ordering boundary.

The RFC identifies the old limitation as a missing evaluation context during configuration loading. Without that context, the loader could not resolve references to variables or locals even when their values were already available. The design adds an early context that can evaluate those references without waiting for resource operations.

This is why early evaluation does not mean unrestricted dynamic configuration. A local value derived from an input can be available before planning. A local value derived from a resource attribute still depends on the resource. Naming that expression as a local does not change its dependency. The useful distinction is when the value can be resolved, not whether its spelling begins with local.

The backend documentation at the 1.8.0 tag states the operative rule. Backend settings may use variables and locals, but may not depend on state data or provider-defined functions. All values must resolve during tofu init, before state is available.

Backend parameters become ordinary configuration

A local can now supply a backend setting. The versioned documentation illustrates the pattern with an S3 region. A fuller configuration can make the storage destination explicit alongside that local.

locals {
  state_region = "us-east-1"
}

terraform {
  backend "s3" {
    bucket = "example-infrastructure-state"
    key    = "network/terraform.tfstate"
    region = local.state_region
  }
}

The bucket name is illustrative and must identify storage available to the operator. This example demonstrates expression evaluation for a backend setting. It does not provision the backend bucket, because the backend must already be usable when initialization configures state access.

The distinction matters for bootstrapping. A resource in this same configuration cannot create the state destination and then supply its newly discovered properties to the backend that is needed to operate the configuration. Early evaluation permits values that are already available. It does not reverse the relationship between backend initialization and resource management.

Backend settings also have persistence consequences. The documentation says OpenTofu records backend configuration in the local .terraform directory and captures it in saved plans. A variable reference is therefore not a guarantee that the evaluated value stays out of those files. The guidance still recommends conventional credential environment variables or credentials files for backend secrets.

An operator changing a value that affects backend configuration must account for reinitialization. The documentation says backend changes require tofu init again, and saved plans use the backend settings captured when they were created. Parameterization makes the source more reusable, but the selected destination remains part of the operation's identity.

Module installation can use known inputs

The same early context supports module source expressions. The RFC motivates selecting a Git revision through an input instead of repeating a fixed source string throughout a configuration. Module installation can resolve that input before downloading the child configuration.

The implementation does not supply every value available during ordinary module execution. The RFC explicitly excludes per-instance for_each keys from static expressions. It also treats static module outputs as future work, because loading sibling modules in dependency order would require further changes to the configuration builder.

Those restrictions preserve a useful boundary. A deployment input can influence which module source is installed. A resource created by that module cannot supply the source needed to load the module in the first place. The early evaluator must report why a reference crosses into unavailable information.

The implementation pull request identifies another practical limit. Missing variables required during this stage produce an error instead of an interactive prompt. Automation already needs to supply its inputs deliberately, but interactive users should not assume the usual later prompting behavior applies during configuration loading.

I find that distinction between phases worth preserving in the explanation. The feature makes more expressions legal because they can be resolved early. It does not make initialization a partial infrastructure apply or silently obtain values from an existing state to choose where that state should live.

Tests can replace provider operations

The 1.8 testing documentation describes a different way of making module behavior easier to exercise. A mock_provider replaces provider configuration for a test. OpenTofu skips actual resource creation and data retrieval through that provider, generating computed values for the test instead.

Generated values follow type-based rules. Numbers become zero, booleans become false and strings become random alphanumeric text. Collections are empty, while object fields follow the same rules recursively. This makes the substitute predictable in structure without implementing the real provider's domain logic.

Consequently, a module that expects a provider-generated identifier to follow a particular format may need explicit mock defaults. A generated string does not prove the remote service would return an identifier with the required semantics. The documentation permits defaults through mock_resource and mock_data, and allows custom computed values in overrides.

Resource and data overrides provide finer selection than replacing an entire provider configuration. An override_resource or override_data block names a target and can supply computed values. File-level overrides apply across the test file, while a matching run-level override takes precedence for that run. This supports a test that isolates a particular dependency without replacing every operation from the provider.

Module overrides go further by ignoring a called module's configuration and supplying its outputs. An omitted output defaults to null. Such a test can verify how the caller handles a module contract, but it cannot establish that the ignored child configuration implements that contract. These are separate questions a module test suite can address through different runs.

The versioned documentation also notes a limitation on instance selection. Resource, data and module overrides apply across instances of the targeted declaration, rather than selecting a single instance. That constraint matters when the configuration uses repetition and a test intends to replace only one member.

Configuration variants have a defined precedence

OpenTofu 1.8 adds .tofu and .tofu.json configuration files. The file documentation says a .tofu file takes precedence over a .tf file with the same base name. OpenTofu loads the former and ignores the latter, allowing a module to supply an OpenTofu-specific variant.

This is file selection, so the ignored file's declarations do not merge into the selected one. Module authors distributing both variants need to account for the complete contents of the replaced file. The mechanism offers a specific compatibility strategy as the implementations acquire different capabilities.

The release changelog separately records removal of the S3 backend's use_legacy_workflow setting. That is an upgrade consideration for existing backend configuration even when no early expressions are added. OpenTofu 1.8 expands reuse through initialization-time evaluation while retaining explicit rules for backend selection, test substitution and configuration-file precedence.