Summary
Implement the version resolution engine — resolving version keywords (latest, latest:<regex>, latest-allowed, min-required) and semantic version constraints from HCL files into concrete Terraform version numbers.
Parent Epic
Part of #488 — Go Edition: Full Feature Parity Implementation
Motivation
This is the brain of tfenv — the logic that takes a version specifier from .terraform-version or TFENV_TERRAFORM_VERSION and resolves it to a concrete, installable version number. It handles the full keyword vocabulary, regex matching, HCL required_version constraint parsing, and semver comparison. Getting this wrong means installing the wrong Terraform version.
Clean-Room Constraint
This is a clean-room implementation. Contributors MUST NOT read, reference, copy, or adapt source code from tofuutils/tenv, hashicorp/hc-install, or any other third-party tfenv-like tool. The sole reference is tfenv's own Bash source code, documentation, and test suite.
Proposed Design
Package Location
go/internal/resolve/
Version Specifiers
| Specifier |
Behaviour |
Bash Reference |
1.5.0 |
Exact version |
Direct use |
latest |
Latest stable release (excludes pre-release) |
libexec/tfenv-resolve-version |
latest:<regex> |
Latest version matching Go RE2 regex (Bash uses ERE) |
libexec/tfenv-resolve-version |
latest-allowed |
Latest version satisfying required_version constraints in .tf files |
libexec/tfenv-resolve-version |
min-required |
Minimum version satisfying required_version constraints |
libexec/tfenv-min-required |
Regex Dialect
The Bash edition uses POSIX Extended Regular Expressions (ERE). The Go edition uses RE2 (Go's native regex). This is a known divergence — document it clearly. Most user patterns (^1\.5, ^1\.6\.) work identically in both dialects.
HCL Constraint Parsing
For latest-allowed and min-required:
- Find
.tf and .tf.json files in the current directory (or TFENV_DIR)
- Parse
terraform { required_version = "..." } blocks
- Extract version constraint strings
- Parse constraints using
hashicorp/go-version
- Filter remote version list against constraints
- Return latest match (for
latest-allowed) or earliest match (for min-required)
latest-allowed resolution (Bash reference: libexec/tfenv-resolve-version lines 128-162):
The Bash edition parses the first required_version it finds (using grep, not full HCL parsing) and handles these constraint operators:
| Constraint |
Bash Behaviour |
Example |
> X.Y.Z or >= X.Y.Z |
→ latest (any version newer) |
>= 1.0.0 → latest |
<= X.Y.Z |
→ exact version X.Y.Z |
<= 1.5.7 → 1.5.7 |
~> X.Y.Z |
→ latest:^X.Y\. (pessimistic, pin major.minor) |
~> 1.5.0 → latest:^1.5\. |
~> X.Y |
→ latest:^X\. (pessimistic, pin major) |
~> 1.5 → latest:^1\. |
= X.Y.Z or bare X.Y.Z |
→ exact version X.Y.Z |
= 1.5.0 → 1.5.0 |
!= X.Y.Z |
Ignored (Bash skips negations) |
|
| Comma-separated |
Only the FIRST constraint is used (%%,* strips after first comma) |
>= 1.0, < 2.0 → uses >= 1.0 only |
Important: The Bash implementation only reads the FIRST constraint from the FIRST matching required_version line. It does NOT intersect multiple constraints. The Go edition should improve on this by using hashicorp/go-version.Constraints for proper multi-constraint evaluation, but the acceptance tests must verify the Bash-compatible happy-path behaviour first.
min-required resolution (Bash reference: lib/tfenv-min-required.sh):
cat ${path}/{*.tf,*.tf.json} — read all HCL files
grep -Eh '^\s*[^#]*\s*required_version' — find required_version lines (excluding comments)
- Extract version numbers with regex:
[~=!<>]{0,2}\s*([0-9]+\.?){2,3}(-[a-z]+[0-9]+)?
- Skip negation operators (
!=)
- Take the first qualifying version
- Pad with
.0 if missing minor/patch (e.g., 12.0 → 12.0.0)
- Return as the min-required version
v Prefix Handling
Both latest-allowed and exact version specifiers may arrive with a v prefix (e.g., v1.5.0). The resolution engine must strip this prefix early in the pipeline. Reference: lib/tfenv-version-name.sh lines 109-112 and libexec/tfenv-resolve-version lines 118-121.
Resolution Flow
Input: version specifier string
Output: "version:regex" pair (e.g., "latest:^1\.5\." or "1.5.0:^1\.5\.0$")
1. Strip `v` prefix if present
2. If "min-required" → parse .tf files, extract min version, return "min_version:^min_version$"
3. If "latest-allowed" → parse .tf files, interpret constraint, convert to latest:<regex>
4. If "latest:<regex>" → return "latest:<regex>"
5. If "latest" (bare) → return "latest:^[0-9]+\.[0-9]+\.[0-9]+$"
6. If exact version → return "version:^version$"
The resolve output is a version:regex pair. The caller (install, use) then uses the version part to determine if it's latest (needs remote lookup) or exact (can check local first), and the regex part to filter the version list.
Acceptance Criteria
Dependencies
Implementation Notes
Labels
type:feature, priority:high, complexity:large, category:version-resolution
Summary
Implement the version resolution engine — resolving version keywords (
latest,latest:<regex>,latest-allowed,min-required) and semantic version constraints from HCL files into concrete Terraform version numbers.Parent Epic
Part of #488 — Go Edition: Full Feature Parity Implementation
Motivation
This is the brain of tfenv — the logic that takes a version specifier from
.terraform-versionorTFENV_TERRAFORM_VERSIONand resolves it to a concrete, installable version number. It handles the full keyword vocabulary, regex matching, HCLrequired_versionconstraint parsing, and semver comparison. Getting this wrong means installing the wrong Terraform version.Clean-Room Constraint
This is a clean-room implementation. Contributors MUST NOT read, reference, copy, or adapt source code from
tofuutils/tenv,hashicorp/hc-install, or any other third-party tfenv-like tool. The sole reference is tfenv's own Bash source code, documentation, and test suite.Proposed Design
Package Location
go/internal/resolve/Version Specifiers
1.5.0latestlibexec/tfenv-resolve-versionlatest:<regex>libexec/tfenv-resolve-versionlatest-allowedrequired_versionconstraints in.tffileslibexec/tfenv-resolve-versionmin-requiredrequired_versionconstraintslibexec/tfenv-min-requiredRegex Dialect
The Bash edition uses POSIX Extended Regular Expressions (ERE). The Go edition uses RE2 (Go's native regex). This is a known divergence — document it clearly. Most user patterns (
^1\.5,^1\.6\.) work identically in both dialects.HCL Constraint Parsing
For
latest-allowedandmin-required:.tfand.tf.jsonfiles in the current directory (orTFENV_DIR)terraform { required_version = "..." }blockshashicorp/go-versionlatest-allowed) or earliest match (formin-required)latest-allowedresolution (Bash reference:libexec/tfenv-resolve-versionlines 128-162):The Bash edition parses the first
required_versionit finds (usinggrep, not full HCL parsing) and handles these constraint operators:> X.Y.Zor>= X.Y.Zlatest(any version newer)>= 1.0.0→latest<= X.Y.Z<= 1.5.7→1.5.7~> X.Y.Zlatest:^X.Y\.(pessimistic, pin major.minor)~> 1.5.0→latest:^1.5\.~> X.Ylatest:^X\.(pessimistic, pin major)~> 1.5→latest:^1\.= X.Y.Zor bareX.Y.Z= 1.5.0→1.5.0!= X.Y.Z%%,*strips after first comma)>= 1.0, < 2.0→ uses>= 1.0onlyImportant: The Bash implementation only reads the FIRST constraint from the FIRST matching
required_versionline. It does NOT intersect multiple constraints. The Go edition should improve on this by usinghashicorp/go-version.Constraintsfor proper multi-constraint evaluation, but the acceptance tests must verify the Bash-compatible happy-path behaviour first.min-requiredresolution (Bash reference:lib/tfenv-min-required.sh):cat ${path}/{*.tf,*.tf.json}— read all HCL filesgrep -Eh '^\s*[^#]*\s*required_version'— find required_version lines (excluding comments)[~=!<>]{0,2}\s*([0-9]+\.?){2,3}(-[a-z]+[0-9]+)?!=).0if missing minor/patch (e.g.,12.0→12.0.0)vPrefix HandlingBoth
latest-allowedand exact version specifiers may arrive with avprefix (e.g.,v1.5.0). The resolution engine must strip this prefix early in the pipeline. Reference:lib/tfenv-version-name.shlines 109-112 andlibexec/tfenv-resolve-versionlines 118-121.Resolution Flow
The resolve output is a
version:regexpair. The caller (install, use) then uses the version part to determine if it'slatest(needs remote lookup) or exact (can check local first), and the regex part to filter the version list.Acceptance Criteria
1.5.0) resolve to themselveslatestresolves to the newest stable (non-pre-release) versionlatest:<regex>filters versions by RE2 regex and returns newest matchlatest:^1\.5correctly matches only1.5.xversions (not1.50.x)latest-allowedreadsrequired_versionfrom.tffiles and returns the latest satisfying versionmin-requiredreadsrequired_versionfrom.tffiles and returns the minimum satisfying versionrequired_versionconstraints across multiple.tffiles are intersected.tffiles or missingrequired_versionblocks produce clear errors forlatest-allowed/min-requiredlatesthashicorp/go-version(not string comparison)= 1.5.0)Dependencies
Implementation Notes
libexec/tfenv-resolve-versionfor the main resolution logiclibexec/tfenv-min-requiredandlib/tfenv-min-required.shfor min-required logicgrep -e "${regex}"for version filtering — the Go edition should useregexp.Compile()1.1matching1.10) is a known regex anchoring bug — the Go edition should document that RE2 behaves the same way (.is a wildcard) but the acceptance tests should cover this edge caselatest-allowedwith exact constraints) and [tfenv use]latest-allowed#390 (latest-allowedgeneral) are related bugs to handle correctlylatest-allowedinstalling beta) must be handled — pre-release filteringhashicorp/hcl/v2can parse just therequired_versionattribute without needing to understand the full Terraform config schemaLabels
type:feature,priority:high,complexity:large,category:version-resolution