Skip to content

Go Edition: Version file resolution (.terraform-version walk) #493

@Zordrak

Description

@Zordrak

Summary

Implement .terraform-version file discovery and version file resolution for the Go edition with identical precedence rules to the Bash edition.

Parent Epic

Part of #488 — Go Edition: Full Feature Parity Implementation

Motivation

Version file resolution is the core mechanism by which tfenv determines which Terraform version to use. It must walk up directories, handle multiple file formats, strip carriage returns, and respect environment variable overrides. Getting this wrong breaks every downstream command.

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 file resolution component)

Version Precedence (highest to lowest)

  1. TFENV_TERRAFORM_VERSION environment variable
  2. .terraform-version file in current directory
  3. .terraform-version file walking up parent directories to filesystem root
  4. ${TFENV_CONFIG_DIR}/version file (default version)
  5. No version found → error

This matches the Bash edition's behaviour in lib/tfenv-version-file.sh and lib/tfenv-version-name.sh.

File Content Parsing

The .terraform-version file may contain:

  • A literal version: 1.5.0
  • A v-prefixed version: v1.5.0 (strip the v prefix)
  • A keyword: latest, latest:^1.5, latest-allowed, min-required
  • Comments starting with # (strip everything from # to end of line)
  • Leading/trailing whitespace (strip)
  • Windows carriage returns \r (strip with strings.TrimRight)
  • Blank lines (skip)

The Bash edition's read_version_file() function in lib/helpers.sh (lines 113-116) applies these transformations:

sed -e 's/#.*$//' -e 's/[[:space:]]*$//' -e '/^[[:space:]]*$/d' "${file}" | tr -d '\r';

The Go equivalent should:

  1. Read the file content
  2. For each line: strip everything after # (comment removal)
  3. Trim trailing whitespace
  4. Skip blank lines
  5. Strip \r (carriage return)
  6. Return the first non-empty line

Important: Only the FIRST non-empty, non-comment line is used. The version file is not expected to contain multiple version specifiers.

Directory Walk

Starting from the current working directory (or TFENV_DIR if set), walk up parent directories looking for .terraform-version. The walk has TWO separate searches:

  1. First search: Walk up from TFENV_DIR (or $PWD) toward the filesystem root
  2. If not found: Walk up from $HOME toward the filesystem root
  3. If still not found: Fall back to ${TFENV_CONFIG_DIR}/version

Reference: lib/tfenv-version-file.shfind_local_version_file() is called twice: first with ${TFENV_DIR:-${PWD}}, then with ${HOME:-/}.

The walk stops at the filesystem root (detected when root becomes empty after stripping the last / component, or matches //[^/]*$ which is the UNC root pattern for network paths).

TFENV_DIR

TFENV_DIR overrides the starting directory for the .terraform-version walk. This is set by:

  • bin/tfenv sets it to $PWD at startup
  • lib/tfenv-exec.sh overrides it when -chdir=<dir> is detected in terraform arguments

Acceptance Criteria

  • TFENV_TERRAFORM_VERSION env var takes highest precedence
  • .terraform-version in current directory is found
  • .terraform-version walk discovers files in parent directories
  • Walk stops at filesystem root without error
  • ${TFENV_CONFIG_DIR}/version is used as fallback default
  • Error is returned when no version source is found
  • Carriage returns (\r) are stripped from file content
  • Leading and trailing whitespace is stripped
  • Empty files produce a clear error
  • TFENV_DIR overrides the walk starting directory
  • Version file content is returned as-is (keyword resolution is a separate concern)
  • The source of the version (which file, or env var) is tracked for diagnostic purposes
  • Comprehensive unit tests with t.TempDir() fixtures cover all precedence combinations
  • Unit tests cover Windows-style line endings (\r\n)
  • Unit tests cover deeply nested directory structures

Dependencies

Implementation Notes

  • Reference lib/tfenv-version-file.sh for the directory walk logic
  • Reference lib/tfenv-version-name.sh for the full precedence chain including env var override
  • Reference libexec/tfenv-version-file for the user-facing tfenv version-file command (which prints which file was resolved)
  • The version file resolution should return BOTH the resolved content AND the source path (for diagnostics and future tfenv why command)
  • Use filepath.Dir() and filepath.Clean() for the directory walk — handle symlinks correctly
  • Issue Trailing whitespace breaks version resolution #431 (trailing whitespace) and Windows line endings issue #392 (Windows line endings) are existing bugs this implementation should handle correctly from the start

Labels

type:feature, priority:high, complexity:medium, category:version-resolution

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions