8.8 KiB
Module Version Locking
Main issue:
Related links and issues:
- https://github.com/opentofu/opentofu/issues/2495
- https://github.com/opentofu/opentofu/issues/1942
- https://github.com/opentofu/opentofu/issues/1199
- https://github.com/joeaawad/random-scripts/blob/master/terraform-opentofu-versioning.md
- https://github.com/hashicorp/terraform/issues/29503
- https://github.com/opentofu/opentofu/pull/2049
- https://terragrunt.gruntwork.io/docs/features/cas/
Problem Statement
OpenTofu currently lacks a mechanism to lock module versions, similar to how provider versions are locked in .terraform.lock.hcl. This creates challenges for reproducibility and security, as module versions can change unexpectedly between runs or across different environments. Users have expressed the need for a feature that allows them to:
- Lock specific module versions to ensure consistent infrastructure deployments
- Specify version constraints (e.g.,
>= 1.0.0,~> 2.1) for modules - Verify module integrity through checksums, similar to provider verification
- Cache modules efficiently to reduce redundant downloads
Currently, while OpenTofu supports version constraints for modules from registries, these are not locked and can resolve to different versions over time. For git-based and OCI registry modules, users must manually specify exact references (commit SHAs, tags, or digests), which is error-prone and lacks the flexibility of semantic version constraints. This has led to several workarounds in the community, mostly around the .terraform/modules/modules.json file.
Proposed Solution
Introduce a comprehensive module version locking mechanism that extends the existing .terraform.lock.hcl file to include module version information, alongside implementing version constraint support for all module source types.
Design
1. Module Version Constraints
Users will be able to specify version constraints for modules in their configuration files. Local modules should not be versioned.
For Registry Modules:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 3.0.0, < 4.0.0"
}
For Git Modules:
module "example" {
source = "git::https://github.com/example/terraform-module.git"
version = "~> 1.2" # Allows patch updates: 1.2.x and will convert to using ref on the URL
# It could also allow SHA pinning
}
For OCI Registry Modules:
module "network" {
source = "oci://registry.example.com/opentofu/network-module"
version = ">= 2.0.0, < 3.0.0" # Will resolve to appropriate tag
}
Version constraint syntax follows the same syntax as the provider version constraints.
2. Lock File Format
When running tofu init, OpenTofu will resolve module versions based on the specified constraints and record them in .terraform.lock.hcl:
# .terraform.lock.hcl
provider "registry.opentofu.org/hashicorp/aws" {
version = "5.31.0"
# ... existing provider lock information
}
module "vpc" {
version = "3.19.0"
source = "registry.opentofu.org/modules/terraform-aws-modules/vpc/aws"
constraints = ">= 3.0.0, < 4.0.0"
hashes = [
"h1:abcd1234...",
"zh:efgh5678...",
]
}
module "vpc.nested" {
version = "1.2.5"
source = "git::https://github.com/example/terraform-module"
constraints = "~> 1.2"
hashes = [
"h1:ijkl9012...",
]
}
module "network" {
version = "2.3.1"
source = "oci://registry.example.com/opentofu/network-module"
constraints = ">= 2.0.0, < 3.0.0"
hashes = [
"h1:xyz789ab...",
]
}
Module Addressing:
Modules in the lock file are identified by their call name. This creates a clear mapping between configuration and lock file entries. For nested modules (modules calling other modules), the lock file uses a hierarchical naming scheme with dot notation (e.g., vpc.nested).
3. Module Installer Enhancement
The module installer (internal/initwd) will be modified to:
For Registry Modules:
- Query the registry API for available versions
- Resolve version constraints to specific versions
- Download module archives
- Compute checksums of module contents
For Git Modules:
- Parse git repository URLs and refs
- Support version constraints by:
- Using
git ls-remote --tagsto fetch tags efficiently (no full clone needed) - Filtering tags that match strict semantic versioning patterns
- Using
- Compute checksums of the checked-out module directory (excluding
.git) - Cache cloned repositories indexed by URL hash (not raw URL to prevent credential leaks)
For OCI Registry Modules:
- Parse OCI registry URLs and image references
- Support version constraints by:
- Querying the OCI registry API for available tags
- Filtering tags that match semantic versioning patterns
- Resolving constraints against available tags
- Using the
tagquery parameter to specify resolved version (e.g.,?tag=2.3.1) - Supporting
digestparameter for immutable references when needed
- Download module package archives from OCI registry
- Compute checksums of the extracted module contents
- Use OCI credentials from standard locations (Docker config, credential helpers)
- Cache downloaded artifacts indexed by registry/repository/digest
For Local Modules:
- No version locking or caching
- Continue current behavior of direct path reference
4. Version Constraint Resolution
Conflict Resolution:
If different modules require incompatible versions of the same transitive dependency, OpenTofu will:
- Fail fast with a clear error message
- Display the conflicting constraints and their sources
- Suggest resolution strategies (e.g., updating constraints)
Example error:
Error: Conflicting module version requirements
Module "app" requires module "common" version >= 2.0.0
Module "utils" requires module "common" version < 2.0.0
No version of module "common" satisfies both constraints.
To resolve, update the version constraints in:
- modules/app/main.tf
- modules/utils/main.tf
5. Checksum Calculation
Module checksums will be calculated as:
For Registry Modules:
- Hash the downloaded module in the
.terraform/modulesfolder - Support multiple hash formats for cross-platform verification
For Git and other VCS Modules:
- Walk the module directory tree
- Hash file contents in deterministic order (sorted by path)
- Exclude version control directories (
.git,.svn, etc.)
For OCI Registry Modules:
- Extract the module archive from the OCI artifact layer
- Walk the extracted module directory tree
- Hash file contents in deterministic order (sorted by path)
- Use the same hashing algorithm as other module types for consistency
- Optionally validate against OCI artifact digest for additional integrity verification
User Workflows
Updating Module Versions
To update modules to newer versions within the specified constraints:
# Update all modules
$ tofu init -upgrade
This will:
- Resolve the latest versions that satisfy the constraints
- Download the new module versions
- Update the lock file with new versions and checksums
- Display which modules were updated
Verifying Module Integrity
OpenTofu will verify module checksums on every init:
$ tofu init
Initializing modules...
- vpc in terraform-aws-modules/vpc/aws 3.19.0
Verified checksum matches lock file
Error: Module checksum verification failed
Module "example" has a checksum that doesn't match the lock file.
This may indicate the module has been modified or tampered with.
Expected: h1:ijkl9012...
Got: h1:mnop3456...
To update the lock file with the new checksum, run:
tofu init -upgrade
Migration Guide
For Existing Projects
When upgrading to OpenTofu with module locking support:
-
First
tofu initafter upgrade:$ tofu init Initializing modules... - vpc in terraform-aws-modules/vpc/aws 3.19.0 OpenTofu has created a lock file .terraform.lock.hcl to record module versions. Include this file in your version control repository. -
Lock file is automatically generated with currently resolved versions
-
No breaking changes to existing workflows - version constraints in config continue to work
Backward Compatibility
- Lock files without module entries remain valid
- OpenTofu reads existing provider-only lock files
- New lock files can include both providers and modules
- No changes required to existing configurations
Future Considerations
- Module Caching?
Add support for caching modules: https://github.com/opentofu/opentofu/issues/1199 maybe using a similar approach to Terragrunt https://terragrunt.gruntwork.io/docs/features/cas/
- How should OpenTofu handle git modules without semantic version tags?
How should we treat the tags for version constraints? Should we support both semantic versioning and ref?