Skip to content

Go Edition: Exec / terraform shim (multi-call binary) #502

@Zordrak

Description

@Zordrak

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

  1. Resolve version: use version file resolution chain (env var → .terraform-version walk → default)
  2. Determine binary path: ${TFENV_CONFIG_DIR}/versions/${version}/terraform
  3. If binary not found and TFENV_AUTO_INSTALL=true → install it
  4. If binary not found and auto-install is off → error with clear message
  5. Exec the real terraform binary with all original arguments
  6. On Unix: use syscall.Exec() (replaces the process — no child process)
  7. 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

  • Binary invoked as terraform resolves version and exec's the real terraform
  • All terraform arguments are passed through unchanged
  • -chdir=<dir> is detected and used for version file resolution
  • Exit code from real terraform is passed through exactly
  • TFENV_AUTO_INSTALL=true triggers install of missing versions
  • TFENV_AUTO_INSTALL=false with missing version produces clear error
  • On Unix, syscall.Exec() replaces the process (no child process overhead)
  • On Windows, stdout/stdin/stderr are correctly piped to child process
  • tfenv init creates a working terraform hardlink next to the binary
  • tfenv init falls back to copy if hardlinks are unsupported
  • tfenv init is idempotent
  • No shim output goes to stdout (only stderr)
  • Acceptance tests verify shim invocation with version resolution
  • Acceptance tests verify -chdir handling
  • Acceptance tests verify exit code passthrough
  • Acceptance tests verify auto-install behaviour

Dependencies

Implementation Notes

Labels

type:feature, priority:high, complexity:medium, category:platform

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