Skip to content

Go Edition: Uninstall command #500

@Zordrak

Description

@Zordrak

Summary

Implement the tfenv uninstall command for the Go edition with full parity to the Bash edition, including multi-version uninstall, version resolution, and version-file fallback.

Parent Epic

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

Motivation

Users need to remove installed Terraform versions to reclaim disk space or clean up old versions. The Bash edition supports multiple versions in a single call, version resolution keywords (latest, latest:<regex>), reading from version files when no argument is given, and TFENV_TERRAFORM_VERSION override. The Go edition must match all of this.

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

Command Interface

tfenv uninstall <version> [<version2> ...]
tfenv uninstall latest
tfenv uninstall latest:<regex>
tfenv uninstall                          # reads from version file or TFENV_TERRAFORM_VERSION

Version Collection

The versions to uninstall are collected from (in priority order):

  1. Command-line arguments — one or more version specifiers
  2. TFENV_TERRAFORM_VERSION — if no args given and env var is set
  3. Version file chain — if no args and no env var, read from .terraform-version walk or ${TFENV_CONFIG_DIR}/version
  4. Error — if none of the above produce a version

Reference: libexec/tfenv-uninstall lines 105-134.

Single Version Uninstall Flow (uninstall_single_version)

For each version specifier:

  1. Reject min-required — not a valid uninstall target
  2. Reject latest-allowed — not a valid uninstall target
  3. Version resolution for latest keywords:
    • latest:<regex> → search locally installed versions matching regex, pick newest
    • latest (no regex) → search locally installed versions, pick newest
    • Exact version → use as-is, anchored with ^version$ regex
  4. Match against locally installed versions (via tfenv list output, stripped of markers and arch info)
  5. Verify ${TFENV_CONFIG_DIR}/versions/${version}/terraform exists
  6. Remove the version directory recursively (os.RemoveAll())
  7. Print confirmation: Terraform v${version} is successfully uninstalled

Reference: libexec/tfenv-uninstall lines 65-100.

Multi-Version Uninstall

When multiple arguments are given, each is uninstalled independently. If one fails, the others still proceed. The command tracks failures and returns non-zero exit code if ANY uninstall failed.

The Bash edition runs each uninstall in a subshell so that log 'error' (which calls exit 1) doesn't kill the loop. The Go edition should catch errors per-version and continue.

Post-Uninstall Cleanup

After all uninstalls complete, if the ${TFENV_CONFIG_DIR}/versions/ directory is empty, remove it with os.Remove() (not RemoveAll — only succeeds if actually empty). This is a courtesy cleanup, not an error if it fails.

Reference: libexec/tfenv-uninstall lines 141-143.

Path Traversal Protection

The version string MUST be validated before constructing filesystem paths. Reject any version containing /, \, .., or null bytes. This prevents path traversal attacks where a malicious .terraform-version file could cause deletion of arbitrary directories.

Acceptance Criteria

  • tfenv uninstall 1.5.0 removes the 1.5.0 version directory
  • tfenv uninstall 1.5.0 1.4.0 uninstalls both versions
  • tfenv uninstall latest uninstalls the newest locally installed version
  • tfenv uninstall latest:^1.5 uninstalls the newest installed 1.5.x version
  • tfenv uninstall (no args) reads version from .terraform-version or default version file
  • TFENV_TERRAFORM_VERSION is respected when no args given
  • tfenv uninstall min-required produces clear error ("unsupported option for uninstall")
  • tfenv uninstall latest-allowed produces clear error ("unsupported option for uninstall")
  • Error if version is not installed (clear message, non-zero exit)
  • Multi-version: continues past individual failures, returns non-zero if any failed
  • Empty versions/ directory is cleaned up after last version removed
  • Exit code 0 on full success, non-zero if any version failed
  • Does not affect other installed versions
  • Path traversal protection: rejects versions containing .., /, \, or null bytes
  • Acceptance tests verify single uninstall, multi-version uninstall, keyword resolution, and error cases

Dependencies

Implementation Notes

  • Reference libexec/tfenv-uninstall — 145 lines including standalone boilerplate. The actual logic is lines 65-145.
  • Key Bash behaviours to match:
    • Lines 70-72: min-required and latest-allowed rejected as unsupported
    • Lines 74-88: Version resolution — latest:regex, latest, and exact version handling
    • Lines 90: Match version against tfenv-list output (stripped of *, whitespace, arch info)
    • Lines 92-100: Check binary exists, remove directory, confirm
    • Lines 105-134: Version collection from args, TFENV_TERRAFORM_VERSION, or version file
    • Lines 136-140: Loop with failure counting (subshell per version)
    • Lines 142-143: Cleanup empty versions directory
  • Issue Add the option to uninstall several versions #449 (uninstall several versions) — the Bash edition already supports this; ensure parity
  • The Go edition uses os.RemoveAll() instead of rm -r
  • Version matching against installed versions must strip the * prefix and (arch) suffix from tfenv list output — or better, query the versions directory directly

Labels

type:feature, priority:high, complexity:small, category:uninstall

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