Summary
Implement the terraform shim — the multi-call binary mode that intercepts terraform invocations, resolves the correct version, and exec's the real binary.
Parent Epic
Part of #488 — Go Edition: Full Feature Parity Implementation
Motivation
The terraform shim is the invisible backbone of tfenv — it intercepts every terraform command, resolves the correct version from .terraform-version files, optionally auto-installs missing versions, and then delegates to the real Terraform binary. In the Go edition, this is implemented via the multi-call binary pattern: the same binary behaves differently depending on whether it's invoked as tfenv or terraform.
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/shim/
Multi-Call Detection
In cmd/tfenv/main.go, check filepath.Base(os.Args[0]):
terraform or terraform.exe → shim mode
tfenv or tfenv.exe → command mode (existing CLI dispatch)
Shim Flow
- Resolve version: use version file resolution chain (env var → .terraform-version walk → default)
- Determine binary path:
${TFENV_CONFIG_DIR}/versions/${version}/terraform
- If binary not found and
TFENV_AUTO_INSTALL=true → install it
- If binary not found and auto-install is off → error with clear message
- Exec the real terraform binary with all original arguments
- On Unix: use
syscall.Exec() (replaces the process — no child process)
- On Windows: use
os/exec.Command() with stdin/stdout/stderr passthrough (no syscall.Exec on Windows)
Argument Passthrough
ALL arguments are passed through unchanged to the real terraform binary. The shim does not parse, modify, or intercept terraform arguments. Exception: it must detect -chdir to set TFENV_DIR for correct version file resolution.
-chdir Handling
Terraform's -chdir=<dir> flag changes the working directory for terraform. The shim must detect this argument and use <dir> as the starting point for .terraform-version file resolution (via TFENV_DIR).
Exit Code Passthrough
The shim must pass through terraform's exit code exactly. If terraform exits 1, the shim exits 1. This is critical for CI pipelines and scripting.
tfenv init Subcommand
tfenv init creates the terraform hardlink (or copy) next to the tfenv binary:
- Determine the path of the running binary (
os.Executable())
- Create a hardlink named
terraform in the same directory
- If hardlinks are not supported (some filesystems, Windows without privileges), fall back to copying the binary
- Idempotent: safe to re-run
Stdout/Stderr Separation
The shim must NOT write anything to stdout — all shim logging goes to stderr. This prevents contamination of terraform's output, which is critical for terraform output and other commands that produce machine-readable stdout (addresses issue #310 and #374).
Acceptance Criteria
Dependencies
Implementation Notes
Labels
type:feature, priority:high, complexity:medium, category:platform
Summary
Implement the terraform shim — the multi-call binary mode that intercepts
terraforminvocations, resolves the correct version, and exec's the real binary.Parent Epic
Part of #488 — Go Edition: Full Feature Parity Implementation
Motivation
The terraform shim is the invisible backbone of tfenv — it intercepts every
terraformcommand, resolves the correct version from.terraform-versionfiles, optionally auto-installs missing versions, and then delegates to the real Terraform binary. In the Go edition, this is implemented via the multi-call binary pattern: the same binary behaves differently depending on whether it's invoked astfenvorterraform.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/shim/Multi-Call Detection
In
cmd/tfenv/main.go, checkfilepath.Base(os.Args[0]):terraformorterraform.exe→ shim modetfenvortfenv.exe→ command mode (existing CLI dispatch)Shim Flow
${TFENV_CONFIG_DIR}/versions/${version}/terraformTFENV_AUTO_INSTALL=true→ install itsyscall.Exec()(replaces the process — no child process)os/exec.Command()with stdin/stdout/stderr passthrough (nosyscall.Execon Windows)Argument Passthrough
ALL arguments are passed through unchanged to the real terraform binary. The shim does not parse, modify, or intercept terraform arguments. Exception: it must detect
-chdirto setTFENV_DIRfor correct version file resolution.-chdirHandlingTerraform's
-chdir=<dir>flag changes the working directory for terraform. The shim must detect this argument and use<dir>as the starting point for.terraform-versionfile resolution (viaTFENV_DIR).Exit Code Passthrough
The shim must pass through terraform's exit code exactly. If terraform exits 1, the shim exits 1. This is critical for CI pipelines and scripting.
tfenv initSubcommandtfenv initcreates theterraformhardlink (or copy) next to thetfenvbinary:os.Executable())terraformin the same directoryStdout/Stderr Separation
The shim must NOT write anything to stdout — all shim logging goes to stderr. This prevents contamination of terraform's output, which is critical for
terraform outputand other commands that produce machine-readable stdout (addresses issue #310 and #374).Acceptance Criteria
terraformresolves version and exec's the real terraform-chdir=<dir>is detected and used for version file resolutionTFENV_AUTO_INSTALL=truetriggers install of missing versionsTFENV_AUTO_INSTALL=falsewith missing version produces clear errorsyscall.Exec()replaces the process (no child process overhead)tfenv initcreates a workingterraformhardlink next to the binarytfenv initfalls back to copy if hardlinks are unsupportedtfenv initis idempotent-chdirhandlingDependencies
TFENV_CONFIG_DIR,TFENV_AUTO_INSTALLImplementation Notes
bin/terraformfor the Bash shimlib/tfenv-exec.shandlibexec/tfenv-execfor the exec logicexecs terraform — the Go edition does the same but nativelysyscall.Execon Unix is preferred because it avoids signal handling complexity — the terraform process becomes PID 1 of the sessionLabels
type:feature,priority:high,complexity:medium,category:platform