├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── bin ├── terraform └── tfenv ├── lib ├── bashlog.sh ├── helpers.sh ├── tfenv-exec.sh ├── tfenv-min-required.sh ├── tfenv-version-file.sh └── tfenv-version-name.sh ├── libexec ├── tfenv---version ├── tfenv-exec ├── tfenv-help ├── tfenv-init ├── tfenv-install ├── tfenv-list ├── tfenv-list-remote ├── tfenv-min-required ├── tfenv-pin ├── tfenv-resolve-version ├── tfenv-uninstall ├── tfenv-use ├── tfenv-version-file └── tfenv-version-name ├── share └── hashicorp-keys.pgp └── test ├── install_deps.sh ├── run.sh ├── test_install_and_use.sh ├── test_list.sh ├── test_symlink.sh ├── test_uninstall.sh ├── test_use_latestallowed.sh └── test_use_minrequired.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 'Test' 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | branches: 9 | - 'master' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | 14 | # Only checking latest Linux/UNIX on pulls to save execution times 15 | test-master-pulls: 16 | if: github.event_name == 'pull_request' 17 | runs-on: "${{ matrix.os }}" 18 | strategy: 19 | matrix: 20 | os: 21 | - 'macos-latest' 22 | - 'ubuntu-latest' 23 | steps: 24 | - uses: 'actions/checkout@v4' 25 | with: 26 | fetch-depth: 1 27 | - name: 'Install Dependencies' 28 | run: './test/install_deps.sh' 29 | - name: 'Run all tests' 30 | run: './test/run.sh' 31 | shell: 'bash' 32 | 33 | - uses: 'docker/setup-buildx-action@v3' 34 | if: contains(matrix.os, 'ubuntu') 35 | with: 36 | install: true 37 | - uses: 'docker/build-push-action@v5' 38 | if: contains(matrix.os, 'ubuntu') 39 | with: 40 | context: . 41 | load: true 42 | tags: "tfenv-terraform:${{ github.sha }}" 43 | - name: 'Check Dockerfile' 44 | if: contains(matrix.os, 'ubuntu') 45 | run: | 46 | expect=1.2.3; 47 | got="$(docker run -e "TFENV_TERRAFORM_VERSION=${expect}" "tfenv-terraform:${{ github.sha }}" version)"; 48 | echo "${got}" | tee /dev/stderr | grep -e 'Terraform v1.2.3' 49 | 50 | # When we push to master, test everything in order to guarantee releases 51 | test-master-pushes: 52 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 53 | runs-on: "${{ matrix.os }}" 54 | strategy: 55 | matrix: 56 | os: 57 | - 'macos-11' 58 | - 'macos-10.15' 59 | - 'ubuntu-20.04' 60 | - 'ubuntu-18.04' 61 | - 'windows-2019' 62 | steps: 63 | - uses: 'actions/checkout@v4' 64 | with: 65 | fetch-depth: 1 66 | - name: 'Install Dependencies' 67 | run: './test/install_deps.sh' 68 | - name: 'Run all tests' 69 | run: './test/run.sh' 70 | shell: 'bash' 71 | 72 | - uses: 'docker/setup-buildx-action@v3' 73 | if: contains(matrix.os, 'ubuntu') 74 | with: 75 | install: true 76 | - uses: 'docker/build-push-action@v5' 77 | if: contains(matrix.os, 'ubuntu') 78 | with: 79 | context: . 80 | load: true 81 | tags: 'tfenv-terraform:latest' 82 | - name: 'Check Dockerfile' 83 | if: contains(matrix.os, 'ubuntu') 84 | run: | 85 | expect=1.2.3; 86 | got="$(docker run -e "TFENV_TERRAFORM_VERSION=${expect}" tfenv-terraform:latest version)"; 87 | echo "${got}" | tee /dev/stderr | grep -e 'Terraform v1.2.3' 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | versions/ 2 | version 3 | .terraform-version 4 | bin/terraform-* 5 | /use-gnupg 6 | /use-gpgv 7 | .*.swp 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | env: 3 | - CLICOLOR=1 4 | matrix: 5 | include: 6 | - os: windows 7 | language: shell 8 | - os: linux 9 | dist: bionic 10 | - os: linux 11 | dist: xenial 12 | - os: linux 13 | dist: trusty 14 | - os: osx 15 | osx_image: xcode11.4 16 | - os: osx 17 | osx_image: xcode11.2 # OS X 10.14 18 | - os: osx 19 | osx_image: xcode10.3 20 | - os: osx 21 | osx_image: xcode10.1 # OS X 10.13 22 | - os: osx 23 | osx_image: xcode9.4 24 | script: 25 | - ./test/run.sh 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.0 (July 15, 2022) 2 | 3 | * BREAKING CHANGE: Don't install ggrep on macs automatically; it's just not good practice. ggrep now a pre-requisite for Mac. 4 | * BREAKING CHANGE: When TFENV_AUTO_INSTALL=true, tfenv use will now attempt to install a matching version when no matching version is installed already, including defaulting to latest if no version is specified at the command line or in overrides (Mike Peachey ) 5 | * MAJOR THANKS: Extreme performance improvements for bashlog (Zoltán Reegn & Andrew Bradley ) 6 | * NEW FEATURE: Add support for netrc for private mirrors (Jack Parsons ) 7 | * NEW FEATURE: Support reversed list of versions from remote (Mike Peachey ) 8 | * FIX: Try to fix RC sorting order in use. Do not invoke version on use as it's meaningless and confusing when defaults are overridden (Mike Peachey ) 9 | * FIX: Improve mktemp error message (Mike Peachey ) 10 | * FIX: #282 - Update warning message when no PGP in use (Mike Peachey ) 11 | * FIX: #299 - When AUTO_INSTALL, tfenv use will attempt to install, including default latest (Mike Peachey ) 12 | * FIX: #315 - ugly 'no such file or directory' message when setting terraform version for the first time (Mike Peachey ) 13 | * FIX: #318 - Updated documentation regarding files instructing PGP usage (Mike Peachey ) 14 | * FIX: Add test of auto-installing min-required version (Oliver Ford ) 15 | * FIX: #300: `min-required` not evaluated for auto-install (Oliver Ford ) 16 | * FIX: #340 - Unbound variable (Mike Peachey ) 17 | * FIX: Install ggrep during mac tests (Mike Peachey ) 18 | * FIX: Update README.md for WSL users (Panu Valtanen ) 19 | * FIX: Look up correct versions location in tfenv-pin and set tfenv-pin as executable (Aaron Madlon-Kay ) 20 | * FIX: Handle new alpha versioning syntax (Dylan Turner ) 21 | * FIX: Add missing exports (Andrew Bradley ) 22 | * FIX: Extract some libexec commands into helper functions (Andrew Bradley ) 23 | * FIX: ARM/ARM64 support (Volodymyr Samusia ) 24 | * FIX: Docs clarification for arm achitecture (Kuba ) 25 | * FIX: Use macos-11 tests (Stephen ) 26 | * FIX: README.md - Suggest shallow git clone for regular usage (Aurélien Joga ) 27 | * FIX: Support redirects on downloads (Troy Ready ) 28 | * FIX: README.md - Support Apple Silicon (Greg Dubicki ) 29 | 30 | ## 2.2.3 (Feb 9, 2022) 31 | 32 | * Fix: mktemp not working correctly on Alpine Linux (#285) 33 | * Add support of ARM64 (#280) 34 | * Add support for tf.json files on min-required (#277) 35 | * Fix issue #210 - allow non-numeric values for DEBUG (#274) 36 | * Download latest version if user uses regex and TFENV_AUTO_INSTALL is true (#272) 37 | * Add tfenv pin command (#270) 38 | 39 | ## 2.2.2 (May 6, 2021) 40 | 41 | * remove trust from revoked signing key as of hcsec-2021-12 42 | * fix installation of versions signed by revoked key by forcing to use the new key 43 | 44 | ## 2.2.1 (April 29, 2021) 45 | 46 | * hcsec-2021-12 (#257) 47 | 48 | ## 2.2.0 (February 06, 2021) 49 | 50 | * Convert GitHub CI from Travis CI to Github Actions 51 | * Fix min-required after it was broken by 2.1.0 (#235) 52 | * Min-required recursive lookup was dangerously broken. Removed the recursion that should never have been (#237) 53 | * Fix the failure of tfenv list when no default was set (#236) 54 | * Add init command (#240) 55 | * Use ggrep on Mac with Homebrew (#218) 56 | 57 | ## 2.1.0 (January 30, 2021) 58 | 59 | * Update tfenv-min-required to search root before recursing (#203) 60 | * Terraform 0.13.0 support (#191) 61 | * Add Arch Linux install instructions via Arch User Repository (AUR) (#201) 62 | * min-required correctly finds tagged release versions (#206) 63 | * install: make keybase a fall-through verification variant (#213) 64 | * Feature/add TFENV_TERRAFORM_VERSION env var (#222) 65 | * Document version-name command (#224) 66 | * Fix signature verification bypass due to insufficient hashsum checking (#212) 67 | * Fix keybase login exit code handling (#188) 68 | * Fix bug on MacOS when using CLICOLOR=1 (#152) 69 | * Improved error handling in tfenv-list-remote-curl (#186) 70 | * Test in Windows (#140) 71 | * force tfenv to write over existing zip if it exists (#169) 72 | * Remove the versions directory when the last version is uninstalled (#128) 73 | * Add support for sha256sum command (#170) 74 | * Adding freebsd support (#133) 75 | * Improve shell script synatx (#174) 76 | * Begrudging Bash 3.x Compatability because of macOS (#181) 77 | 78 | ## 2.0.0 (April 20, 2020) 79 | 80 | * New logging and debugging library 81 | * Massive testing, logging and loading refactoring 82 | * Fix to 'use' logic: don't overwrite .terraform-version files 83 | * Fix #167 - Never invoke use automatically on install - multiple code and testing changes for new logic 84 | * Fix to not use 0.12.22 during testing which reports its version incorrectly 85 | * Introduce tfenv-resolve-version to deduplicate translation of requested version into actual version 86 | * README.md updates 87 | * Fix #176 - New parameter TFENV_AUTO_INSTALL to handle the version specified by `use` or a `.terraform-version` file not being installed 88 | 89 | ## 1.0.2 (October 29, 2019) 90 | 91 | * Fix failing 0.11.15-oci version, Add additional tests for 0.11.15-oci and alphas, betas and rcs #145 92 | * Fix to README section link for .terraform-version file #146 93 | 94 | ## 1.0.1 (June 22, 2019) 95 | 96 | * Fix '--version' flag to return version from CHANGELOG.md when not running from a git checkout. 97 | 98 | ## 1.0.0 (June 9, 2019) 99 | 100 | * A number of bugfixes and basic script improvements 101 | * latest keyword doesn't include unstable releases unless specified by regex 102 | * Support GnuPG tools for signature verification #109 103 | * Add support for Cygwin #81 104 | 105 | ## 0.6.0 (November 1, 2017) 106 | 107 | * Support msys2 bash.exe #75 108 | * Version from sources #73 109 | * run tfenv as a neighbour with full path (to keep vscode and whoever doesn't respect %path, happy) #72 110 | * Add current version in list command #69 111 | * Version file #67 112 | * [DOC] Add link to puppet module for automatic tfenv installation #64 113 | 114 | ## 0.5.2 (July 23, 2017) 115 | 116 | * Switch TLS option for curl depending on the version of macOS 117 | 118 | ## 0.5.1 (July 21, 2017) 119 | 120 | * Fix version detection 121 | * Add support for ARM architecture 122 | 123 | ## 0.5.0 (=0.4.4) (July 14, 2017) 124 | 125 | * Immediately switch version after installation 126 | * Add uninstall command 127 | 128 | ## 0.4.3 (April 12, 2017) 129 | 130 | * Move temporary directory from /tmp to mktemp 131 | * Upgrade tfenv-install logging 132 | * Prevent interactive prompting from keybase 133 | 134 | ## 0.4.2 (April 9, 2017) 135 | 136 | * Add support for verifying downloads of Terraform 137 | 138 | ## 0.4.1 (March 8, 2017) 139 | 140 | * Update error_and_die functions to better report their source location 141 | * libexec/tfenv-version-name: Respect `latest` & `latest:` syntax in .terraform-version 142 | * Extension and development of test suite standards 143 | 144 | ## 0.4.0 (March 6, 2017) 145 | 146 | * Add capability to specify `latest` or `latest:` in the `use` and `install` actions. 147 | * Add error_and_die functions to standardise error output 148 | * Specify --tlsv1.2 to curl requests to remote repo. TLSv1.2 required; supported by but not auto-selected by older NSS. 149 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASH_VERSION=5 2 | FROM "docker.io/bash:${BASH_VERSION}" 3 | 4 | # Runtime dependencies 5 | RUN apk add --no-cache --purge \ 6 | curl \ 7 | ; 8 | 9 | ARG TFENV_VERSION=3.0.0 10 | RUN wget -O /tmp/tfenv.tar.gz "https://github.com/tfutils/tfenv/archive/refs/tags/v${TFENV_VERSION}.tar.gz" \ 11 | && tar -C /tmp -xf /tmp/tfenv.tar.gz \ 12 | && mv "/tmp/tfenv-${TFENV_VERSION}/bin"/* /usr/local/bin/ \ 13 | && mkdir -p /usr/local/lib/tfenv \ 14 | && mv "/tmp/tfenv-${TFENV_VERSION}/lib" /usr/local/lib/tfenv/ \ 15 | && mv "/tmp/tfenv-${TFENV_VERSION}/libexec" /usr/local/lib/tfenv/ \ 16 | && mkdir -p /usr/local/share/licenses \ 17 | && mv "/tmp/tfenv-${TFENV_VERSION}/LICENSE" /usr/local/share/licenses/tfenv \ 18 | && rm -rf /tmp/tfenv* \ 19 | ; 20 | ENV TFENV_ROOT /usr/local/lib/tfenv 21 | 22 | ENV TFENV_CONFIG_DIR /var/tfenv 23 | VOLUME /var/tfenv 24 | 25 | # Default to latest; user-specifiable 26 | ENV TFENV_TERRAFORM_VERSION latest 27 | ENTRYPOINT ["/usr/local/bin/terraform"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Shinichi Ishimura 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI Test](https://github.com/tfutils/tfenv/actions/workflows/test.yml/badge.svg)](https://github.com/tfutils/tfenv/actions/workflows/test.yml) 2 | 3 | # tfenv 4 | 5 | [Terraform](https://www.terraform.io/) version manager inspired by [rbenv](https://github.com/rbenv/rbenv) 6 | 7 | ## Support 8 | 9 | Currently tfenv supports the following OSes 10 | 11 | - macOS 12 | - 64bit 13 | - Arm (Apple Silicon) 14 | - Linux 15 | - 64bit 16 | - Arm 17 | - Windows (64bit) - only tested in git-bash - currently presumed failing due to symlink issues in git-bash 18 | 19 | ## Installation 20 | 21 | ### Automatic 22 | 23 | Install via Homebrew 24 | 25 | ```console 26 | brew install tfenv 27 | ``` 28 | 29 | Install via Arch User Repository (AUR) 30 | 31 | ```console 32 | yay --sync tfenv 33 | ``` 34 | 35 | Install via puppet 36 | 37 | Using puppet module [sergk-tfenv](https://github.com/SergK/puppet-tfenv) 38 | 39 | ```puppet 40 | include ::tfenv 41 | ``` 42 | 43 | ### Manual 44 | 45 | 1. Check out tfenv into any path (here is `${HOME}/.tfenv`) 46 | 47 | ```console 48 | git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv 49 | ``` 50 | 51 | 2. Add `~/.tfenv/bin` to your `$PATH` any way you like 52 | 53 | bash: 54 | ```console 55 | echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile 56 | ``` 57 | zsh: 58 | ```console 59 | $ echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.zprofile 60 | ``` 61 | 62 | For WSL users: 63 | ```bash 64 | echo 'export PATH=$PATH:$HOME/.tfenv/bin' >> ~/.bashrc 65 | ``` 66 | 67 | OR you can make symlinks for `tfenv/bin/*` scripts into a path that is already added to your `$PATH` (e.g. `/usr/local/bin`) `OSX/Linux Only!` 68 | 69 | ```console 70 | ln -s ~/.tfenv/bin/* /usr/local/bin 71 | ``` 72 | 73 | On Ubuntu/Debian touching `/usr/local/bin` might require sudo access, but you can create `${HOME}/bin` or `${HOME}/.local/bin` and on next login it will get added to the session `$PATH` 74 | or by running `. ${HOME}/.profile` it will get added to the current shell session's `$PATH`. 75 | 76 | ```console 77 | mkdir -p ~/.local/bin/ 78 | . ~/.profile 79 | ln -s ~/.tfenv/bin/* ~/.local/bin 80 | which tfenv 81 | ``` 82 | 83 | ## Usage 84 | 85 | ### tfenv install [version] 86 | 87 | Install a specific version of Terraform. 88 | 89 | If no parameter is passed, the version to use is resolved automatically via [TFENV\_TERRAFORM\_VERSION environment variable](#tfenv_terraform_version) or [.terraform-version files](#terraform-version-file), in that order of precedence, i.e. TFENV\_TERRAFORM\_VERSION, then .terraform-version. The default is 'latest' if none are found. 90 | 91 | If a parameter is passed, available options: 92 | 93 | - `x.y.z` [Semver 2.0.0](https://semver.org/) string specifying the exact version to install 94 | - `latest` is a syntax to install latest version 95 | - `latest:` is a syntax to install latest version matching regex (used by grep -e) 96 | - `latest-allowed` is a syntax to scan your Terraform files to detect which version is maximally allowed. 97 | - `min-required` is a syntax to scan your Terraform files to detect which version is minimally required. 98 | 99 | See [required_version](https://developer.hashicorp.com/terraform/language/settings) docs. Also [see min-required & latest-allowed](#min-required) section below. 100 | 101 | ```console 102 | $ tfenv install 103 | $ tfenv install 0.7.0 104 | $ tfenv install latest 105 | $ tfenv install latest:^0.8 106 | $ tfenv install latest-allowed 107 | $ tfenv install min-required 108 | ``` 109 | 110 | If `shasum` is present in the path, tfenv will verify the download against Hashicorp's published sha256 hash. 111 | If [keybase](https://keybase.io/) is available in the path it will also verify the signature for those published hashes using Hashicorp's published public key. 112 | 113 | You can opt-in to using GnuPG tools for PGP signature verification if keybase is not available: 114 | 115 | Where `TFENV_INSTALL_DIR` is for example, `~/.tfenv` or `/usr/local/Cellar/tfenv/` 116 | 117 | ```console 118 | echo 'trust-tfenv: yes' > ${TFENV_INSTALL_DIR}/use-gpgv 119 | tfenv install 120 | ``` 121 | 122 | The `trust-tfenv` directive means that verification uses a copy of the 123 | Hashicorp OpenPGP key found in the tfenv repository. Skipping that directive 124 | means that the Hashicorp key must be in the existing default trusted keys. 125 | Use the file `${TFENV_INSTALL_DIR}/use-gnupg` to instead invoke the full `gpg` tool and 126 | see web-of-trust status; beware that a lack of trust path will not cause a 127 | validation failure. 128 | 129 | #### .terraform-version 130 | 131 | If you use a [.terraform-version](#terraform-version-file) file, `tfenv install` (no argument) will install the version written in it. 132 | 133 | 134 | #### min-required & latest-allowed 135 | 136 | Please note that we don't do semantic version range parsing but use first ever found version as the candidate for minimally required one. It is up to the user to keep the definition reasonable. I.e. 137 | 138 | ```terraform 139 | // this will detect 0.12.3 140 | terraform { 141 | required_version = "<0.12.3, >= 0.10.0" 142 | } 143 | ``` 144 | 145 | ```terraform 146 | // this will detect 0.10.8 (the latest 0.10.x release) 147 | terraform { 148 | required_version = "~> 0.10.0, <0.12.3" 149 | } 150 | ``` 151 | 152 | ### Environment Variables 153 | 154 | #### TFENV 155 | 156 | ##### `TFENV_ARCH` 157 | 158 | String (Default: `amd64`) 159 | 160 | Specify architecture. Architecture other than the default amd64 can be specified with the `TFENV_ARCH` environment variable 161 | 162 | Note: Default changes to `arm64` for versions that have arm64 builds available when `$(uname -m)` matches `aarch64* | arm64*` 163 | 164 | ```console 165 | TFENV_ARCH=arm64 tfenv install 0.7.9 166 | ``` 167 | 168 | ##### `TFENV_AUTO_INSTALL` 169 | 170 | String (Default: true) 171 | 172 | Should tfenv automatically install terraform if the version specified by defaults or a .terraform-version file is not currently installed. 173 | 174 | ```console 175 | TFENV_AUTO_INSTALL=false terraform plan 176 | ``` 177 | 178 | ```console 179 | terraform use 180 | ``` 181 | 182 | ##### `TFENV_CURL_OUTPUT` 183 | 184 | Integer (Default: 2) 185 | 186 | Set the mechanism used for displaying download progress when downloading terraform versions from the remote server. 187 | 188 | * 2: v1 Behaviour: Pass `-#` to curl 189 | * 1: Use curl default 190 | * 0: Pass `-s` to curl 191 | 192 | ##### `TFENV_DEBUG` 193 | 194 | Integer (Default: 0) 195 | 196 | Set the debug level for TFENV. 197 | 198 | * 0: No debug output 199 | * 1: Simple debug output 200 | * 2: Extended debug output, with source file names and interactive debug shells on error 201 | * 3: Debug level 2 + Bash execution tracing 202 | 203 | ##### `TFENV_REMOTE` 204 | 205 | String (Default: https://releases.hashicorp.com) 206 | 207 | To install from a remote other than the default 208 | 209 | ```console 210 | TFENV_REMOTE=https://example.jfrog.io/artifactory/hashicorp 211 | ``` 212 | 213 | ##### `TFENV_REVERSE_REMOTE` 214 | 215 | Integer (Default: 0) 216 | 217 | When using a custom remote, such as Artifactory, instead of the Hashicorp servers, 218 | the list of terraform versions returned by the curl of the remote directory may be inverted. 219 | In this case the `latest` functionality will not work as expected because it expects the 220 | versions to be listed in order of release date from newest to oldest. If your remote 221 | is instead providing a list that is oldes-first, set `TFENV_REVERSE_REMOTE=1` and 222 | functionality will be restored. 223 | 224 | ```console 225 | TFENV_REVERSE_REMOTE=1 tfenv list-remote 226 | ``` 227 | 228 | ##### `TFENV_CONFIG_DIR` 229 | 230 | Path (Default: `$TFENV_ROOT`) 231 | 232 | The path to a directory where the local terraform versions and configuration files exist. 233 | 234 | ```console 235 | TFENV_CONFIG_DIR="$XDG_CONFIG_HOME/tfenv" 236 | ``` 237 | 238 | ##### `TFENV_TERRAFORM_VERSION` 239 | 240 | String (Default: "") 241 | 242 | If not empty string, this variable overrides Terraform version, specified in [.terraform-version files](#terraform-version-file). 243 | `latest` and `latest:` syntax are also supported. 244 | [`tfenv install`](#tfenv-install-version) and [`tfenv use`](#tfenv-use-version) command also respects this variable. 245 | 246 | e.g. 247 | 248 | ```console 249 | TFENV_TERRAFORM_VERSION=latest:^0.11. terraform --version 250 | ``` 251 | 252 | ##### `TFENV_NETRC_PATH` 253 | 254 | String (Default: "") 255 | 256 | If not empty string, this variable specifies the credentials file used to access the remote location (useful if used in conjunction with TFENV_REMOTE). 257 | 258 | e.g. 259 | 260 | ```console 261 | TFENV_NETRC_PATH="$PWD/.netrc.tfenv" 262 | ``` 263 | 264 | #### Bashlog Logging Library 265 | 266 | ##### `BASHLOG_COLOURS` 267 | 268 | Integer (Default: 1) 269 | 270 | To disable colouring of console output, set to 0. 271 | 272 | 273 | ##### `BASHLOG_DATE_FORMAT` 274 | 275 | String (Default: +%F %T) 276 | 277 | The display format for the date as passed to the `date` binary to generate a datestamp used as a prefix to: 278 | 279 | * `FILE` type log file lines. 280 | * Each console output line when `BASHLOG_EXTRA=1` 281 | 282 | ##### `BASHLOG_EXTRA` 283 | 284 | Integer (Default: 0) 285 | 286 | By default, console output from tfenv does not print a date stamp or log severity. 287 | 288 | To enable this functionality, making normal output equivalent to FILE log output, set to 1. 289 | 290 | ##### `BASHLOG_FILE` 291 | 292 | Integer (Default: 0) 293 | 294 | Set to 1 to enable plain text logging to file (FILE type logging). 295 | 296 | The default path for log files is defined by /tmp/$(basename $0).log 297 | Each executable logs to its own file. 298 | 299 | e.g. 300 | 301 | ```console 302 | BASHLOG_FILE=1 tfenv use latest 303 | ``` 304 | 305 | will log to `/tmp/tfenv-use.log` 306 | 307 | ##### `BASHLOG_FILE_PATH` 308 | 309 | String (Default: /tmp/$(basename ${0}).log) 310 | 311 | To specify a single file as the target for all FILE type logging regardless of the executing script. 312 | 313 | ##### `BASHLOG_I_PROMISE_TO_BE_CAREFUL_CUSTOM_EVAL_PREFIX` 314 | 315 | String (Default: "") 316 | 317 | *BE CAREFUL - MISUSE WILL DESTROY EVERYTHING YOU EVER LOVED* 318 | 319 | This variable allows you to pass a string containing a command that will be executed using `eval` in order to produce a prefix to each console output line, and each FILE type log entry. 320 | 321 | e.g. 322 | 323 | ```console 324 | BASHLOG_I_PROMISE_TO_BE_CAREFUL_CUSTOM_EVAL_PREFIX='echo "${$$} "' 325 | ``` 326 | will prefix every log line with the calling process' PID. 327 | 328 | ##### `BASHLOG_JSON` 329 | 330 | Integer (Default: 0) 331 | 332 | Set to 1 to enable JSON logging to file (JSON type logging). 333 | 334 | The default path for log files is defined by /tmp/$(basename $0).log.json 335 | Each executable logs to its own file. 336 | 337 | e.g. 338 | 339 | ```console 340 | BASHLOG_JSON=1 tfenv use latest 341 | ``` 342 | 343 | will log in JSON format to `/tmp/tfenv-use.log.json` 344 | 345 | JSON log content: 346 | 347 | `{"timestamp":"","level":"","message":""}` 348 | 349 | ##### `BASHLOG_JSON_PATH` 350 | 351 | String (Default: /tmp/$(basename ${0}).log.json) 352 | 353 | To specify a single file as the target for all JSON type logging regardless of the executing script. 354 | 355 | ##### `BASHLOG_SYSLOG` 356 | 357 | Integer (Default: 0) 358 | 359 | To log to syslog using the `logger` binary, set this to 1. 360 | 361 | The basic functionality is thus: 362 | 363 | ```console 364 | local tag="${BASHLOG_SYSLOG_TAG:-$(basename "${0}")}"; 365 | local facility="${BASHLOG_SYSLOG_FACILITY:-local0}"; 366 | local pid="${$}"; 367 | logger --id="${pid}" -t "${tag}" -p "${facility}.${severity}" "${syslog_line}" 368 | ``` 369 | 370 | ##### `BASHLOG_SYSLOG_FACILITY` 371 | 372 | String (Default: local0) 373 | 374 | The syslog facility to specify when using SYSLOG type logging. 375 | 376 | ##### `BASHLOG_SYSLOG_TAG` 377 | 378 | String (Default: $(basename $0)) 379 | 380 | The syslog tag to specify when using SYSLOG type logging. 381 | 382 | Defaults to the PID of the calling process. 383 | 384 | 385 | 386 | ### tfenv use [version] 387 | 388 | Switch a version to use 389 | 390 | If no parameter is passed, the version to use is resolved automatically via [.terraform-version files](#terraform-version-file) or [TFENV\_TERRAFORM\_VERSION environment variable](#tfenv_terraform_version) (TFENV\_TERRAFORM\_VERSION takes precedence), defaulting to 'latest' if none are found. 391 | 392 | `latest` is a syntax to use the latest installed version 393 | 394 | `latest:` is a syntax to use latest installed version matching regex (used by grep -e) 395 | 396 | `min-required` will switch to the version minimally required by your terraform sources (see above `tfenv install`) 397 | 398 | ```console 399 | $ tfenv use 400 | $ tfenv use min-required 401 | $ tfenv use 0.7.0 402 | $ tfenv use latest 403 | $ tfenv use latest:^0.8 404 | ``` 405 | 406 | Note: `tfenv use latest` or `tfenv use latest:` will find the latest matching version that is already installed. If no matching versions are installed, and TFENV_AUTO_INSTALL is set to `true` (which is the default) the the latest matching version in the remote repository will be installed and used. 407 | 408 | ### tfenv uninstall <version> 409 | 410 | Uninstall a specific version of Terraform 411 | `latest` is a syntax to uninstall latest version 412 | `latest:` is a syntax to uninstall latest version matching regex (used by grep -e) 413 | 414 | ```console 415 | $ tfenv uninstall 0.7.0 416 | $ tfenv uninstall latest 417 | $ tfenv uninstall latest:^0.8 418 | ``` 419 | 420 | ### tfenv list 421 | 422 | List installed versions 423 | 424 | ```console 425 | $ tfenv list 426 | * 0.10.7 (set by /opt/tfenv/version) 427 | 0.9.0-beta2 428 | 0.8.8 429 | 0.8.4 430 | 0.7.0 431 | 0.7.0-rc4 432 | 0.6.16 433 | 0.6.2 434 | 0.6.1 435 | ``` 436 | 437 | ### tfenv list-remote 438 | 439 | List installable versions 440 | 441 | ```console 442 | $ tfenv list-remote 443 | 0.9.0-beta2 444 | 0.9.0-beta1 445 | 0.8.8 446 | 0.8.7 447 | 0.8.6 448 | 0.8.5 449 | 0.8.4 450 | 0.8.3 451 | 0.8.2 452 | 0.8.1 453 | 0.8.0 454 | 0.8.0-rc3 455 | 0.8.0-rc2 456 | 0.8.0-rc1 457 | 0.8.0-beta2 458 | 0.8.0-beta1 459 | 0.7.13 460 | 0.7.12 461 | ... 462 | ``` 463 | 464 | ## .terraform-version file 465 | 466 | If you put a `.terraform-version` file on your project root, or in your home directory, tfenv detects it and uses the version written in it. If the version is `latest` or `latest:`, the latest matching version currently installed will be selected. 467 | 468 | Note, that [TFENV\_TERRAFORM\_VERSION environment variable](#tfenv_terraform_version) can be used to override version, specified by `.terraform-version` file. 469 | 470 | ```console 471 | $ cat .terraform-version 472 | 0.6.16 473 | 474 | $ terraform version 475 | Terraform v0.6.16 476 | 477 | Your version of Terraform is out of date! The latest version 478 | is 0.7.3. You can update by downloading from www.terraform.io 479 | 480 | $ echo 0.7.3 > .terraform-version 481 | 482 | $ terraform version 483 | Terraform v0.7.3 484 | 485 | $ echo latest:^0.8 > .terraform-version 486 | 487 | $ terraform version 488 | Terraform v0.8.8 489 | 490 | $ TFENV_TERRAFORM_VERSION=0.7.3 terraform --version 491 | Terraform v0.7.3 492 | ``` 493 | 494 | ## Upgrading 495 | 496 | ```console 497 | git --git-dir=~/.tfenv/.git pull 498 | ``` 499 | 500 | ## Uninstalling 501 | 502 | ```console 503 | rm -rf /some/path/to/tfenv 504 | ``` 505 | 506 | ## LICENSE 507 | 508 | - [tfenv itself](https://github.com/tfutils/tfenv/blob/master/LICENSE) 509 | - [rbenv](https://github.com/rbenv/rbenv/blob/master/LICENSE) 510 | - tfenv partially uses rbenv's source code 511 | -------------------------------------------------------------------------------- /bin/terraform: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | #################################### 5 | # Ensure we can execute standalone # 6 | #################################### 7 | 8 | function early_death() { 9 | echo "[FATAL] ${0}: ${1}" >&2; 10 | exit 1; 11 | }; 12 | 13 | if [ -z "${TFENV_ROOT:-""}" ]; then 14 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 15 | readlink_f() { 16 | local target_file="${1}"; 17 | local file_name; 18 | 19 | while [ "${target_file}" != "" ]; do 20 | cd "${target_file%/*}" || early_death "Failed to 'cd \$(${target_file%/*})' while trying to determine TFENV_ROOT"; 21 | file_name="${target_file##*/}" || early_death "Failed to '\"${target_file##*/}\"' while trying to determine TFENV_ROOT"; 22 | target_file="$(readlink "${file_name}")"; 23 | done; 24 | 25 | echo "$(pwd -P)/${file_name}"; 26 | }; 27 | TFENV_SHIM=$(readlink_f "${0}") 28 | TFENV_ROOT="${TFENV_SHIM%/*/*}"; 29 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to determine TFENV_ROOT"; 30 | else 31 | TFENV_ROOT="${TFENV_ROOT%/}"; 32 | fi; 33 | export TFENV_ROOT; 34 | 35 | if [ -n "${TFENV_HELPERS:-""}" ]; then 36 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 37 | else 38 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && >&2 echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 39 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 40 | log 'debug' 'Helpers sourced successfully'; 41 | else 42 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 43 | fi; 44 | fi; 45 | 46 | # Ensure libexec and bin are in $PATH 47 | for dir in libexec bin; do 48 | case ":${PATH}:" in 49 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 50 | *) 51 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 52 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 53 | ;; 54 | esac; 55 | done; 56 | 57 | ##################### 58 | # Begin Script Body # 59 | ##################### 60 | 61 | log 'debug' "program=\"${0##*/}\""; 62 | 63 | declare tfenv_path="${TFENV_ROOT}/bin/tfenv"; 64 | 65 | tfenv-exec "$@"; 66 | -------------------------------------------------------------------------------- /bin/tfenv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | #################################### 5 | # Ensure we can execute standalone # 6 | #################################### 7 | 8 | function early_death() { 9 | echo "[FATAL] ${0}: ${1}" >&2; 10 | exit 1; 11 | }; 12 | 13 | if [ -z "${TFENV_ROOT:-""}" ]; then 14 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 15 | readlink_f() { 16 | local target_file="${1}"; 17 | local file_name; 18 | 19 | while [ "${target_file}" != "" ]; do 20 | cd "${target_file%/*}" || early_death "Failed to 'cd \$(${target_file%/*})' while trying to determine TFENV_ROOT"; 21 | file_name="${target_file##*/}" || early_death "Failed to '\"${target_file##*/}\"' while trying to determine TFENV_ROOT"; 22 | target_file="$(readlink "${file_name}")"; 23 | done; 24 | 25 | echo "$(pwd -P)/${file_name}"; 26 | }; 27 | TFENV_SHIM=$(readlink_f "${0}") 28 | TFENV_ROOT="${TFENV_SHIM%/*/*}"; 29 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to determine TFENV_ROOT"; 30 | 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && >&2 echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | # Ensure libexec and bin are in $PATH 48 | for dir in libexec bin; do 49 | case ":${PATH}:" in 50 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 51 | *) 52 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 53 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 54 | ;; 55 | esac; 56 | done; 57 | 58 | ##################### 59 | # Begin Script Body # 60 | ##################### 61 | 62 | declare arg="${1:-""}"; 63 | 64 | log 'debug' "Setting TFENV_DIR to ${PWD}"; 65 | export TFENV_DIR="${PWD}"; 66 | 67 | abort() { 68 | log 'debug' 'Aborting...'; 69 | { 70 | if [ "${#}" -eq 0 ]; then 71 | cat -; 72 | else 73 | echo "tfenv: ${*}"; 74 | fi; 75 | } >&2; 76 | }; 77 | 78 | log 'debug' "tfenv argument is: ${arg}"; 79 | 80 | case "${arg}" in 81 | "") 82 | log 'debug' 'No argument provided, dumping version and help and aborting'; 83 | { 84 | tfenv---version; 85 | tfenv-help; 86 | } | abort && exit 1; 87 | exit 1; 88 | ;; 89 | -v | --version ) 90 | log 'debug' 'tfenv version requested...'; 91 | exec tfenv---version; 92 | ;; 93 | -h | --help ) 94 | log 'debug' 'tfenv help requested...'; 95 | exec tfenv-help; 96 | ;; 97 | *) 98 | log 'debug' "Long argument provided: ${arg}"; 99 | command_path="$(command -v "tfenv-${arg}" || true)"; 100 | log 'debug' "Resulting command-path: ${command_path}"; 101 | if [ -z "${command_path}" ]; then 102 | { 103 | echo "No such command '${arg}'"; 104 | tfenv-help; 105 | } | abort && exit 1; 106 | fi; 107 | shift 1; 108 | log 'debug' "Exec: \"${command_path}\" \"$*\""; 109 | exec "${command_path}" "$@"; 110 | ;; 111 | esac; 112 | 113 | log 'error' 'This line should not be reachable. Something catastrophic has occurred'; 114 | -------------------------------------------------------------------------------- /lib/bashlog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | function _log_exception() { 6 | ( 7 | BASHLOG_FILE=0; 8 | BASHLOG_JSON=0; 9 | BASHLOG_SYSLOG=0; 10 | 11 | log 'error' "Logging Exception: $*"; 12 | ); 13 | }; 14 | export -f _log_exception; 15 | 16 | function log() { 17 | local syslog="${BASHLOG_SYSLOG:-0}"; 18 | local file="${BASHLOG_FILE:-0}"; 19 | local json="${BASHLOG_JSON:-0}"; 20 | local stdout_extra="${BASHLOG_EXTRA:-0}"; 21 | 22 | if [ "${file}" -eq 1 ] || [ "${json}" -eq 1 ] || [ "${stdout_extra}" -eq 1 ]; then 23 | local date_format="${BASHLOG_DATE_FORMAT:-+%F %T}"; 24 | local date="$(date "${date_format}")"; 25 | local date_s="$(date "+%s")"; 26 | fi 27 | local file_path="${BASHLOG_FILE_PATH:-/tmp/${0##*/}.log}"; 28 | local json_path="${BASHLOG_JSON_PATH:-/tmp/${0##*/}.log.json}"; 29 | 30 | local tag="${BASHLOG_SYSLOG_TAG:-${0##*/})}"; 31 | local facility="${BASHLOG_SYSLOG_FACILITY:-local0}"; 32 | local pid="${$}"; 33 | 34 | local level="${1}"; 35 | local upper="$(echo "${level}" | awk '{print toupper($0)}')"; 36 | local debug_level="${TFENV_DEBUG:-0}"; 37 | local stdout_colours="${BASHLOG_COLOURS:-1}"; 38 | 39 | local custom_eval_prefix="${BASHLOG_I_PROMISE_TO_BE_CAREFUL_CUSTOM_EVAL_PREFIX:-""}"; 40 | 41 | shift 1; 42 | 43 | local line="$@"; 44 | 45 | # RFC 5424 46 | # 47 | # Numerical Severity 48 | # Code 49 | # 50 | # 0 Emergency: system is unusable 51 | # 1 Alert: action must be taken immediately 52 | # 2 Critical: critical conditions 53 | # 3 Error: error conditions 54 | # 4 Warning: warning conditions 55 | # 5 Notice: normal but significant condition 56 | # 6 Informational: informational messages 57 | # 7 Debug: debug-level messages 58 | 59 | local severities_DEBUG=7; 60 | local severities_INFO=6; 61 | local severities_NOTICE=5; # Unused 62 | local severities_WARN=4; 63 | local severities_ERROR=3; 64 | local severities_CRIT=2; # Unused 65 | local severities_ALERT=1; # Unused 66 | local severities_EMERG=0; # Unused 67 | 68 | local severity_var="severities_${upper}"; 69 | local severity="${!severity_var:-3}"; 70 | 71 | if [ "${debug_level}" -gt 0 ] || [ "${severity}" -lt 7 ]; then 72 | 73 | if [ "${syslog}" -eq 1 ]; then 74 | local syslog_line="${upper}: ${line}"; 75 | 76 | logger \ 77 | --id="${pid}" \ 78 | -t "${tag}" \ 79 | -p "${facility}.${severity}" \ 80 | "${syslog_line}" \ 81 | || _log_exception "logger --id=\"${pid}\" -t \"${tag}\" -p \"${facility}.${severity}\" \"${syslog_line}\""; 82 | fi; 83 | 84 | if [ "${file}" -eq 1 ]; then 85 | local file_line="${date} [${upper}] ${line}"; 86 | 87 | if [ -n "${custom_eval_prefix}" ]; then 88 | file_line="$(eval "${custom_eval_prefix}")${file_line}"; 89 | fi; 90 | 91 | echo -e "${file_line}" >> "${file_path}" \ 92 | || _log_exception "echo -e \"${file_line}\" >> \"${file_path}\""; 93 | fi; 94 | 95 | if [ "${json}" -eq 1 ]; then 96 | local json_line="$(printf '{"timestamp":"%s","level":"%s","message":"%s"}' "${date_s}" "${level}" "${line}")"; 97 | echo -e "${json_line}" >> "${json_path}" \ 98 | || _log_exception "echo -e \"${json_line}\" >> \"${json_path}\""; 99 | fi; 100 | 101 | fi; 102 | 103 | local colours_DEBUG='\033[34m' # Blue 104 | local colours_INFO='\033[32m' # Green 105 | local colours_NOTICE='' # Unused 106 | local colours_WARN='\033[33m' # Yellow 107 | local colours_ERROR='\033[31m' # Red 108 | local colours_CRIT='' # Unused 109 | local colours_ALERT='' # Unused 110 | local colours_EMERG='' # Unused 111 | local colours_DEFAULT='\033[0m' # Default 112 | 113 | local norm="${colours_DEFAULT}"; 114 | local colour_var="colours_${upper}"; 115 | local colour="${!colour_var:-\033[31m}"; 116 | 117 | local std_line; 118 | if [ "${debug_level}" -le 1 ]; then 119 | std_line="${line}"; 120 | elif [ "${debug_level}" -ge 2 ]; then 121 | std_line="${0}: ${line}"; 122 | fi; 123 | 124 | if [ "${stdout_extra}" -eq 1 ]; then 125 | std_line="${date} [${upper}] ${std_line}"; 126 | fi; 127 | 128 | if [ -n "${custom_eval_prefix}" ]; then 129 | std_line="$(eval "${custom_eval_prefix}")${std_line}"; 130 | fi; 131 | 132 | if [ "${stdout_colours}" -eq 1 ]; then 133 | std_line="${colour}${std_line}${norm}"; 134 | fi; 135 | 136 | # Standard Output (Pretty) 137 | case "${level}" in 138 | 'info'|'warn') 139 | echo -e "${std_line}"; 140 | ;; 141 | 'debug') 142 | if [ "${debug_level}" -gt 0 ]; then 143 | # We are debugging to STDERR on purpose 144 | # tfenv relies on STDOUT between libexecs to function 145 | echo -e "${std_line}" >&2; 146 | fi; 147 | ;; 148 | 'error') 149 | echo -e "${std_line}" >&2; 150 | if [ "${debug_level}" -gt 1 ]; then 151 | echo -e "Here's a shell for debugging the current environment. 'exit 0' to resume script from here. Non-zero exit code will abort - parent shell will terminate." >&2; 152 | bash || exit "${?}"; 153 | else 154 | exit 1; 155 | fi; 156 | ;; 157 | *) 158 | log 'error' "Undefined log level trying to log: $*"; 159 | ;; 160 | esac 161 | }; 162 | export -f log; 163 | 164 | declare prev_cmd="null"; 165 | declare this_cmd="null"; 166 | trap 'prev_cmd=${this_cmd:-null}; this_cmd=$BASH_COMMAND' DEBUG \ 167 | && log debug 'DEBUG trap set' \ 168 | || log 'error' 'DEBUG trap failed to set'; 169 | 170 | # This is an option if you want to log every single command executed, 171 | # but it will significantly impact script performance and unit tests will fail 172 | 173 | #trap 'prev_cmd=$this_cmd; this_cmd=$BASH_COMMAND; log debug $this_cmd' DEBUG \ 174 | # && log debug 'DEBUG trap set' \ 175 | # || log 'error' 'DEBUG trap failed to set'; 176 | -------------------------------------------------------------------------------- /lib/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | if [ -z "${TFENV_ROOT:-""}" ]; then 6 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 7 | readlink_f() { 8 | local target_file="${1}"; 9 | local file_name; 10 | 11 | while [ "${target_file}" != "" ]; do 12 | cd "${target_file%/*}" || early_death "Failed to 'cd \$(${target_file%/*})' while trying to determine TFENV_ROOT"; 13 | file_name="${target_file##*/}" || early_death "Failed to '\"${target_file##*/}\"' while trying to determine TFENV_ROOT"; 14 | target_file="$(readlink "${file_name}")"; 15 | done; 16 | 17 | echo "$(pwd -P)/${file_name}"; 18 | }; 19 | TFENV_SHIM=$(readlink_f "${0}") 20 | TFENV_ROOT="${TFENV_SHIM%/*/*}"; 21 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to determine TFENV_ROOT"; 22 | else 23 | TFENV_ROOT="${TFENV_ROOT%/}"; 24 | fi; 25 | export TFENV_ROOT; 26 | 27 | if [ -z "${TFENV_CONFIG_DIR:-""}" ]; then 28 | TFENV_CONFIG_DIR="$TFENV_ROOT"; 29 | else 30 | TFENV_CONFIG_DIR="${TFENV_CONFIG_DIR%/}"; 31 | fi 32 | export TFENV_CONFIG_DIR; 33 | 34 | if [ "${TFENV_DEBUG:-0}" -gt 0 ]; then 35 | # Only reset DEBUG if TFENV_DEBUG is set, and DEBUG is unset or already a number 36 | if [[ "${DEBUG:-0}" =~ ^[0-9]+$ ]] && [ "${DEBUG:-0}" -gt "${TFENV_DEBUG:-0}" ]; then 37 | export DEBUG="${TFENV_DEBUG:-0}"; 38 | fi; 39 | if [[ "${TFENV_DEBUG}" -gt 2 ]]; then 40 | export PS4='+ [${BASH_SOURCE##*/}:${LINENO}] '; 41 | set -x; 42 | fi; 43 | fi; 44 | 45 | function load_bashlog () { 46 | source "${TFENV_ROOT}/lib/bashlog.sh"; 47 | }; 48 | export -f load_bashlog; 49 | 50 | if [ "${TFENV_DEBUG:-0}" -gt 0 ] ; then 51 | # our shim below cannot be used when debugging is enabled 52 | load_bashlog; 53 | else 54 | # Shim that understands to no-op for debug messages, and defers to 55 | # full bashlog for everything else. 56 | function log () { 57 | if [ "$1" != 'debug' ] ; then 58 | # Loading full bashlog will overwrite the `log` function 59 | load_bashlog; 60 | log "$@"; 61 | fi; 62 | }; 63 | export -f log; 64 | fi; 65 | 66 | # Curl wrapper to switch TLS option for each OS 67 | function curlw () { 68 | local TLS_OPT="--tlsv1.2"; 69 | 70 | # Check if curl is 10.12.6 or above 71 | if [[ -n "$(command -v sw_vers 2>/dev/null)" && ("$(sw_vers)" =~ 10\.12\.([6-9]|[0-9]{2}) || "$(sw_vers)" =~ 10\.1[3-9]) ]]; then 72 | TLS_OPT=""; 73 | fi; 74 | 75 | if [[ ! -z "${TFENV_NETRC_PATH:-""}" ]]; then 76 | NETRC_OPT="--netrc-file ${TFENV_NETRC_PATH}"; 77 | else 78 | NETRC_OPT=""; 79 | fi; 80 | 81 | curl ${TLS_OPT} ${NETRC_OPT} "$@"; 82 | }; 83 | export -f curlw; 84 | 85 | function check_active_version() { 86 | local v="${1}"; 87 | local maybe_chdir=; 88 | if [ -n "${2:-}" ]; then 89 | maybe_chdir="-chdir=${2}"; 90 | fi; 91 | 92 | local active_version="$(${TFENV_ROOT}/bin/terraform ${maybe_chdir} version | grep '^Terraform')"; 93 | 94 | if ! grep -E "^Terraform v${v}((-dev)|( \([a-f0-9]+\)))?( is already installed)?\$" <(echo "${active_version}"); then 95 | log 'debug' "Expected version ${v} but found ${active_version}"; 96 | return 1; 97 | fi; 98 | 99 | log 'debug' "Active version ${v} as expected"; 100 | return 0; 101 | }; 102 | export -f check_active_version; 103 | 104 | function check_installed_version() { 105 | local v="${1}"; 106 | local bin="${TFENV_CONFIG_DIR}/versions/${v}/terraform"; 107 | [ -n "$(${bin} version | grep -E "^Terraform v${v}((-dev)|( \([a-f0-9]+\)))?$")" ]; 108 | }; 109 | export -f check_installed_version; 110 | 111 | function check_default_version() { 112 | local v="${1}"; 113 | local def="$(cat "${TFENV_CONFIG_DIR}/version")"; 114 | [ "${def}" == "${v}" ]; 115 | }; 116 | export -f check_default_version; 117 | 118 | function cleanup() { 119 | log 'info' 'Performing cleanup'; 120 | local pwd="$(pwd)"; 121 | log 'debug' "Deleting ${pwd}/version"; 122 | rm -rf ./version; 123 | log 'debug' "Deleting ${pwd}/versions"; 124 | rm -rf ./versions; 125 | log 'debug' "Deleting ${pwd}/.terraform-version"; 126 | rm -rf ./.terraform-version; 127 | log 'debug' "Deleting ${pwd}/latest_allowed.tf"; 128 | rm -rf ./latest_allowed.tf; 129 | log 'debug' "Deleting ${pwd}/min_required.tf"; 130 | rm -rf ./min_required.tf; 131 | log 'debug' "Deleting ${pwd}/chdir-dir"; 132 | rm -rf ./chdir-dir; 133 | }; 134 | export -f cleanup; 135 | 136 | function error_and_proceed() { 137 | errors+=("${1}"); 138 | log 'warn' "Test Failed: ${1}"; 139 | }; 140 | export -f error_and_proceed; 141 | 142 | function check_dependencies() { 143 | if [[ $(uname) == 'Darwin' ]] && [ $(which brew) ]; then 144 | if ! [ $(which ggrep) ]; then 145 | log 'error' 'A metaphysical dichotomy has caused this unit to overload and shut down. GNU Grep is a requirement and your Mac does not have it. Consider "brew install grep"'; 146 | fi; 147 | 148 | shopt -s expand_aliases; 149 | alias grep=ggrep; 150 | fi; 151 | }; 152 | export -f check_dependencies; 153 | 154 | source "$TFENV_ROOT/lib/tfenv-exec.sh"; 155 | source "$TFENV_ROOT/lib/tfenv-min-required.sh"; 156 | source "$TFENV_ROOT/lib/tfenv-version-file.sh"; 157 | source "$TFENV_ROOT/lib/tfenv-version-name.sh"; 158 | 159 | export TFENV_HELPERS=1; 160 | -------------------------------------------------------------------------------- /lib/tfenv-exec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | function tfenv-exec() { 6 | for _arg in ${@:1}; do 7 | if [[ "${_arg}" == -chdir=* ]]; then 8 | log 'debug' "Found -chdir arg. Setting TFENV_DIR to: ${_arg#-chdir=}"; 9 | export TFENV_DIR="${PWD}/${_arg#-chdir=}"; 10 | fi; 11 | done; 12 | 13 | log 'debug' 'Getting version from tfenv-version-name'; 14 | TFENV_VERSION="$(tfenv-version-name)" \ 15 | && log 'debug' "TFENV_VERSION is ${TFENV_VERSION}" \ 16 | || { 17 | # Errors will be logged from tfenv-version name, 18 | # we don't need to trouble STDERR with repeat information here 19 | log 'debug' 'Failed to get version from tfenv-version-name'; 20 | return 1; 21 | }; 22 | export TFENV_VERSION; 23 | 24 | if [ ! -d "${TFENV_CONFIG_DIR}/versions/${TFENV_VERSION}" ]; then 25 | if [ "${TFENV_AUTO_INSTALL:-true}" == "true" ]; then 26 | if [ -z "${TFENV_TERRAFORM_VERSION:-""}" ]; then 27 | TFENV_VERSION_SOURCE="$(tfenv-version-file)"; 28 | else 29 | TFENV_VERSION_SOURCE='TFENV_TERRAFORM_VERSION'; 30 | fi; 31 | log 'info' "version '${TFENV_VERSION}' is not installed (set by ${TFENV_VERSION_SOURCE}). Installing now as TFENV_AUTO_INSTALL==true"; 32 | tfenv-install; 33 | else 34 | log 'error' "version '${TFENV_VERSION}' was requested, but not installed and TFENV_AUTO_INSTALL is not 'true'"; 35 | fi; 36 | fi; 37 | 38 | TF_BIN_PATH="${TFENV_CONFIG_DIR}/versions/${TFENV_VERSION}/terraform"; 39 | export PATH="${TF_BIN_PATH}:${PATH}"; 40 | log 'debug' "TF_BIN_PATH added to PATH: ${TF_BIN_PATH}"; 41 | log 'debug' "Executing: ${TF_BIN_PATH} $@"; 42 | 43 | exec "${TF_BIN_PATH}" "$@" \ 44 | || log 'error' "Failed to execute: ${TF_BIN_PATH} $*"; 45 | 46 | return 0; 47 | }; 48 | export -f tfenv-exec; 49 | -------------------------------------------------------------------------------- /lib/tfenv-min-required.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | function tfenv-min-required() { 6 | local path="${1:-${TFENV_DIR:-.}}"; 7 | 8 | local versions="$( echo $(cat ${path}/{*.tf,*.tf.json} 2>/dev/null | grep -Eh '^\s*[^#]*\s*required_version') | grep -o '[~=!<>]\{0,2\}\s*\([0-9]\+\.\?\)\{2,3\}\(-[a-z]\+[0-9]\+\)\?')"; 9 | 10 | if [[ "${versions}" =~ ([~=!<>]{0,2}[[:blank:]]*)([0-9]+[0-9.]+)[^0-9]*(-[a-z]+[0-9]+)? ]]; then 11 | qualifier="${BASH_REMATCH[1]}"; 12 | found_min_required="${BASH_REMATCH[2]}${BASH_REMATCH[3]}"; 13 | if [[ "${qualifier}" =~ ^!= ]]; then 14 | log 'debug' "required_version is a negation - we cannot guess the desired one, skipping."; 15 | else 16 | local min_required_file="$(grep -Hn required_version ${path}/{*.tf,*.tf.json} 2>/dev/null | xargs)"; 17 | 18 | # Probably not an advisable way to choose a terraform version, 19 | # but this is the way this functionality works in terraform: 20 | # add .0 to versions without a minor and/or patch version (e.g. 12.0) 21 | while ! [[ "${found_min_required}" =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; do 22 | found_min_required="${found_min_required}.0"; 23 | done; 24 | 25 | log 'debug' "Determined min-required to be '${found_min_required}' (${min_required_file})"; 26 | echo -en "${found_min_required}"; 27 | return; 28 | fi; 29 | fi; 30 | 31 | log 'debug' 'Appropriate required_version not found, skipping min-required'; 32 | }; 33 | export -f tfenv-min-required; 34 | -------------------------------------------------------------------------------- /lib/tfenv-version-file.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | function find_local_version_file() { 6 | log 'debug' "Looking for a version file in ${1}"; 7 | 8 | local root="${1}"; 9 | 10 | while ! [[ "${root}" =~ ^//[^/]*$ ]]; do 11 | 12 | if [ -e "${root}/.terraform-version" ]; then 13 | log 'debug' "Found at ${root}/.terraform-version"; 14 | echo "${root}/.terraform-version"; 15 | return 0; 16 | else 17 | log 'debug' "Not found at ${root}/.terraform-version"; 18 | fi; 19 | 20 | [ -n "${root}" ] || break; 21 | root="${root%/*}"; 22 | 23 | done; 24 | 25 | log 'debug' "No version file found in ${1}"; 26 | return 1; 27 | }; 28 | export -f find_local_version_file; 29 | 30 | function tfenv-version-file() { 31 | if ! find_local_version_file "${TFENV_DIR:-${PWD}}"; then 32 | if ! find_local_version_file "${HOME:-/}"; then 33 | log 'debug' "No version file found in search paths. Defaulting to TFENV_CONFIG_DIR: ${TFENV_CONFIG_DIR}/version"; 34 | echo "${TFENV_CONFIG_DIR}/version"; 35 | fi; 36 | fi; 37 | }; 38 | export -f tfenv-version-file; 39 | -------------------------------------------------------------------------------- /lib/tfenv-version-name.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | function tfenv-version-name() { 6 | if [[ -z "${TFENV_TERRAFORM_VERSION:-""}" ]]; then 7 | log 'debug' 'We are not hardcoded by a TFENV_TERRAFORM_VERSION environment variable'; 8 | 9 | TFENV_VERSION_FILE="$(tfenv-version-file)" \ 10 | && log 'debug' "TFENV_VERSION_FILE retrieved from tfenv-version-file: ${TFENV_VERSION_FILE}" \ 11 | || log 'error' 'Failed to retrieve TFENV_VERSION_FILE from tfenv-version-file'; 12 | 13 | TFENV_VERSION="$(cat "${TFENV_VERSION_FILE}" || true)" \ 14 | && log 'debug' "TFENV_VERSION specified in TFENV_VERSION_FILE: ${TFENV_VERSION}"; 15 | 16 | TFENV_VERSION_SOURCE="${TFENV_VERSION_FILE}"; 17 | 18 | else 19 | TFENV_VERSION="${TFENV_TERRAFORM_VERSION}" \ 20 | && log 'debug' "TFENV_VERSION specified in TFENV_TERRAFORM_VERSION environment variable: ${TFENV_VERSION}"; 21 | 22 | TFENV_VERSION_SOURCE='TFENV_TERRAFORM_VERSION'; 23 | fi; 24 | 25 | local auto_install="${TFENV_AUTO_INSTALL:-true}"; 26 | 27 | if [[ "${TFENV_VERSION}" == "min-required" ]]; then 28 | log 'debug' 'TFENV_VERSION uses min-required keyword, looking for a required_version in the code'; 29 | 30 | local potential_min_required="$(tfenv-min-required)"; 31 | if [[ -n "${potential_min_required}" ]]; then 32 | log 'debug' "'min-required' converted to '${potential_min_required}'"; 33 | TFENV_VERSION="${potential_min_required}" \ 34 | TFENV_VERSION_SOURCE='terraform{required_version}'; 35 | else 36 | log 'error' 'Specifically asked for min-required via terraform{required_version}, but none found'; 37 | fi; 38 | fi; 39 | 40 | if [[ "${TFENV_VERSION}" =~ ^latest.*$ ]]; then 41 | log 'debug' "TFENV_VERSION uses 'latest' keyword: ${TFENV_VERSION}"; 42 | 43 | if [[ "${TFENV_VERSION}" == latest-allowed ]]; then 44 | TFENV_VERSION="$(tfenv-resolve-version)"; 45 | log 'debug' "Resolved latest-allowed to: ${TFENV_VERSION}"; 46 | fi; 47 | 48 | if [[ "${TFENV_VERSION}" =~ ^latest\:.*$ ]]; then 49 | regex="${TFENV_VERSION##*\:}"; 50 | log 'debug' "'latest' keyword uses regex: ${regex}"; 51 | else 52 | regex="^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 53 | log 'debug' "Version uses latest keyword alone. Forcing regex to match stable versions only: ${regex}"; 54 | fi; 55 | 56 | declare local_version=''; 57 | if [[ -d "${TFENV_CONFIG_DIR}/versions" ]]; then 58 | local_version="$(\find "${TFENV_CONFIG_DIR}/versions/" -type d -exec basename {} \; \ 59 | | tail -n +2 \ 60 | | sort -t'.' -k 1nr,1 -k 2nr,2 -k 3nr,3 \ 61 | | grep -e "${regex}" \ 62 | | head -n 1)"; 63 | 64 | log 'debug' "Resolved ${TFENV_VERSION} to locally installed version: ${local_version}"; 65 | elif [[ "${auto_install}" != "true" ]]; then 66 | log 'error' 'No versions of terraform installed and TFENV_AUTO_INSTALL is not true. Please install a version of terraform before it can be selected as latest'; 67 | fi; 68 | 69 | if [[ "${auto_install}" == "true" ]]; then 70 | log 'debug' "Using latest keyword and auto_install means the current version is whatever is latest in the remote. Trying to find the remote version using the regex: ${regex}"; 71 | remote_version="$(tfenv-list-remote | grep -e "${regex}" | head -n 1)"; 72 | if [[ -n "${remote_version}" ]]; then 73 | if [[ "${local_version}" != "${remote_version}" ]]; then 74 | log 'debug' "The installed version '${local_version}' does not much the remote version '${remote_version}'"; 75 | TFENV_VERSION="${remote_version}"; 76 | else 77 | TFENV_VERSION="${local_version}"; 78 | fi; 79 | else 80 | log 'error' "No versions matching '${requested}' found in remote"; 81 | fi; 82 | else 83 | if [[ -n "${local_version}" ]]; then 84 | TFENV_VERSION="${local_version}"; 85 | else 86 | log 'error' "No installed versions of terraform matched '${TFENV_VERSION}'"; 87 | fi; 88 | fi; 89 | else 90 | log 'debug' 'TFENV_VERSION does not use "latest" keyword'; 91 | 92 | # Accept a v-prefixed version, but strip the v. 93 | if [[ "${TFENV_VERSION}" =~ ^v.*$ ]]; then 94 | log 'debug' "Version Requested is prefixed with a v. Stripping the v."; 95 | TFENV_VERSION="${TFENV_VERSION#v*}"; 96 | fi; 97 | fi; 98 | 99 | if [[ -z "${TFENV_VERSION}" ]]; then 100 | log 'error' "Version could not be resolved (set by ${TFENV_VERSION_SOURCE} or tfenv use )"; 101 | fi; 102 | 103 | if [[ "${TFENV_VERSION}" == min-required ]]; then 104 | TFENV_VERSION="$(tfenv-min-required)"; 105 | fi; 106 | 107 | if [[ ! -d "${TFENV_CONFIG_DIR}/versions/${TFENV_VERSION}" ]]; then 108 | log 'debug' "version '${TFENV_VERSION}' is not installed (set by ${TFENV_VERSION_SOURCE})"; 109 | fi; 110 | 111 | echo "${TFENV_VERSION}"; 112 | }; 113 | export -f tfenv-version-name; 114 | 115 | -------------------------------------------------------------------------------- /libexec/tfenv---version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: Display the version of tfenv 3 | # 4 | # Displays the version number of this tfenv release, including the 5 | # current revision from git, if available. 6 | # 7 | # The format of the git revision is: 8 | # -- 9 | # where `num_commits` is the number of commits since `version` was 10 | # tagged. 11 | 12 | set -uo pipefail; 13 | 14 | #################################### 15 | # Ensure we can execute standalone # 16 | #################################### 17 | 18 | function early_death() { 19 | echo "[FATAL] ${0}: ${1}" >&2; 20 | exit 1; 21 | }; 22 | 23 | if [ -z "${TFENV_ROOT:-""}" ]; then 24 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 25 | readlink_f() { 26 | local target_file="${1}"; 27 | local file_name; 28 | 29 | while [ "${target_file}" != "" ]; do 30 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 31 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 32 | target_file="$(readlink "${file_name}")"; 33 | done; 34 | 35 | echo "$(pwd -P)/${file_name}"; 36 | }; 37 | 38 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 39 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 40 | else 41 | TFENV_ROOT="${TFENV_ROOT%/}"; 42 | fi; 43 | export TFENV_ROOT; 44 | 45 | if [ -n "${TFENV_HELPERS:-""}" ]; then 46 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 47 | else 48 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 49 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 50 | log 'debug' 'Helpers sourced successfully'; 51 | else 52 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 53 | fi; 54 | fi; 55 | 56 | # Ensure libexec and bin are in $PATH 57 | for dir in libexec bin; do 58 | case ":${PATH}:" in 59 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 60 | *) 61 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 62 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 63 | ;; 64 | esac; 65 | done; 66 | 67 | ##################### 68 | # Begin Script Body # 69 | ##################### 70 | 71 | log 'debug' 'Scraping tfenv version from CHANGELOG.md'; 72 | version="$(awk '/^##/{ print $2; exit}' "${TFENV_ROOT}/CHANGELOG.md")" \ 73 | && log 'debug' "Found version '${version}' in CHANGELOG.md" \ 74 | || log 'error' 'Failed to scrape version from CHANGELOG.md'; 75 | 76 | git_revision=""; 77 | if cd "$(dirname ${0})" 2>/dev/null && git remote -v 2>/dev/null | grep -q tfenv; then 78 | log 'debug' 'Git configuration found. Overriding CHANGELOG version from git revision...'; 79 | git_revision="$(git describe --tags HEAD 2>/dev/null || true)"; 80 | log 'debug' "Stripping git revision string from ${git_revision}"; 81 | git_revision="${git_revision#v}"; 82 | fi; 83 | 84 | echo "tfenv ${git_revision:-$version}"; 85 | 86 | exit 0; 87 | -------------------------------------------------------------------------------- /libexec/tfenv-exec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Run an executable with the selected Terraform version 4 | # 5 | # Usage: tfenv exec [arg1 arg2...] 6 | # 7 | # Runs an executable by first preparing PATH so that the selected Terraform 8 | # version's `bin' directory is at the front. 9 | # 10 | # For example, if the currently selected Terraform version is 0.7.0: 11 | # tfenv exec plan 12 | # 13 | # is equivalent to: 14 | # PATH="$TFENV_ROOT/versions/0.7.0/bin:$PATH" terraform plan 15 | 16 | set -uo pipefail; 17 | 18 | #################################### 19 | # Ensure we can execute standalone # 20 | #################################### 21 | 22 | function early_death() { 23 | echo "[FATAL] ${0}: ${1}" >&2; 24 | exit 1; 25 | }; 26 | 27 | if [ -z "${TFENV_ROOT:-""}" ]; then 28 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 29 | readlink_f() { 30 | local target_file="${1}"; 31 | local file_name; 32 | 33 | while [ "${target_file}" != "" ]; do 34 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 35 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 36 | target_file="$(readlink "${file_name}")"; 37 | done; 38 | 39 | echo "$(pwd -P)/${file_name}"; 40 | }; 41 | 42 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 43 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 44 | else 45 | TFENV_ROOT="${TFENV_ROOT%/}"; 46 | fi; 47 | export TFENV_ROOT; 48 | 49 | if [ -n "${TFENV_HELPERS:-""}" ]; then 50 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 51 | else 52 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 53 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 54 | log 'debug' 'Helpers sourced successfully'; 55 | else 56 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 57 | fi; 58 | fi; 59 | 60 | # Ensure libexec and bin are in $PATH 61 | for dir in libexec bin; do 62 | case ":${PATH}:" in 63 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 64 | *) 65 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 66 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 67 | ;; 68 | esac; 69 | done; 70 | 71 | ##################### 72 | # Begin Script Body # 73 | ##################### 74 | 75 | tfenv-exec "$@"; 76 | -------------------------------------------------------------------------------- /libexec/tfenv-help: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | echo 'Usage: tfenv [] 6 | 7 | Commands: 8 | install Install a specific version of Terraform 9 | use Switch a version to use 10 | uninstall Uninstall a specific version of Terraform 11 | list List all installed versions 12 | list-remote List all installable versions 13 | version-name Print current version 14 | init Update environment to use 'tfenv' correctly. 15 | pin Write the current active version to ./.terraform-version 16 | '; 17 | 18 | exit 0; 19 | -------------------------------------------------------------------------------- /libexec/tfenv-init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "${TFENV_DEBUG}" ] && set -x; 4 | 5 | export PATH="${TFENV_ROOT}/bin:${PATH}"; 6 | -------------------------------------------------------------------------------- /libexec/tfenv-install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | #################################### 5 | # Ensure we can execute standalone # 6 | #################################### 7 | 8 | function early_death() { 9 | echo "[FATAL] ${0}: ${1}" >&2; 10 | exit 1; 11 | }; 12 | 13 | if [ -z "${TFENV_ROOT:-""}" ]; then 14 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 15 | readlink_f() { 16 | local target_file="${1}"; 17 | local file_name; 18 | 19 | while [ "${target_file}" != "" ]; do 20 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 21 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 22 | target_file="$(readlink "${file_name}")"; 23 | done; 24 | 25 | echo "$(pwd -P)/${file_name}"; 26 | }; 27 | 28 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 29 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 30 | else 31 | TFENV_ROOT="${TFENV_ROOT%/}"; 32 | fi; 33 | export TFENV_ROOT; 34 | 35 | if [ -n "${TFENV_HELPERS:-""}" ]; then 36 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 37 | else 38 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 39 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 40 | log 'debug' 'Helpers sourced successfully'; 41 | else 42 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 43 | fi; 44 | fi; 45 | 46 | # Ensure libexec and bin are in $PATH 47 | for dir in libexec bin; do 48 | case ":${PATH}:" in 49 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 50 | *) 51 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 52 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 53 | ;; 54 | esac; 55 | done; 56 | 57 | ##################### 58 | # Begin Script Body # 59 | ##################### 60 | 61 | [ "${#}" -gt 1 ] && log 'error' 'usage: tfenv install []'; 62 | 63 | declare requested="${1:-""}"; 64 | 65 | log debug "Resolving version with: tfenv-resolve-version ${requested}"; 66 | declare resolved; 67 | resolved="$(tfenv-resolve-version ${requested})" || log 'error' "Failed to resolve ${requested} version"; 68 | 69 | declare version="${resolved%%\:*}"; 70 | declare regex="${resolved##*\:}"; 71 | 72 | [ -n "${version}" ] || log 'error' 'Version is not specified. This should not be possible as we default to latest'; 73 | 74 | log 'debug' "Processing install for version ${version}, using regex ${regex}"; 75 | 76 | remote_version="$(tfenv-list-remote | grep -e "${regex}" | head -n 1)"; 77 | [ -n "${remote_version}" ] && version="${remote_version}" || log 'error' "No versions matching '${requested:-$version}' found in remote"; 78 | 79 | dst_path="${TFENV_CONFIG_DIR}/versions/${version}"; 80 | if [ -f "${dst_path}/terraform" ]; then 81 | echo "Terraform v${version} is already installed"; 82 | exit 0; 83 | fi; 84 | 85 | case "$(uname -s)" in 86 | Darwin*) 87 | kernel="darwin"; 88 | ;; 89 | MINGW64*) 90 | kernel="windows"; 91 | ;; 92 | MSYS*NT*) 93 | kernel="windows"; 94 | ;; 95 | CYGWIN*NT*) 96 | kernel="windows"; 97 | ;; 98 | FreeBSD*) 99 | kernel="freebsd"; 100 | ;; 101 | *) 102 | kernel="linux"; 103 | ;; 104 | esac; 105 | 106 | # Add support of ARM64 for Linux & Apple Silicon 107 | case "$(uname -m)" in 108 | aarch64* | arm64*) 109 | case "${kernel}" in 110 | "linux") 111 | # There is no arm64 support for versions: 112 | # < 0.11.15 113 | # >= 0.12.0, < 0.12.30 114 | # >= 0.13.0, < 0.13.5 115 | if [[ "${version}" =~ 0\.(([0-9]|10))\.\d* || 116 | "${version}" =~ 0\.11\.(([0-9]|1[0-4]))$ || 117 | "${version}" =~ 0\.12\.(([0-9]|[1-2][0-9]))$ || 118 | "${version}" =~ 0\.13\.[0-4]$ 119 | ]]; then 120 | TFENV_ARCH="${TFENV_ARCH:-amd64}"; 121 | else 122 | TFENV_ARCH="${TFENV_ARCH:-arm64}"; 123 | fi; 124 | ;; 125 | "darwin") 126 | # No Apple Silicon builds before 1.0.2 127 | if [[ "${version}" =~ ^0\.[0-9]+\.[0-9]+ || "${version}" =~ ^1\.0\.[0-1]$ 128 | ]]; then 129 | TFENV_ARCH="${TFENV_ARCH:-amd64}"; 130 | else 131 | TFENV_ARCH="${TFENV_ARCH:-arm64}"; 132 | fi; 133 | ;; 134 | esac; 135 | ;; 136 | *) 137 | TFENV_ARCH="${TFENV_ARCH:-amd64}"; 138 | ;; 139 | esac; 140 | 141 | os="${kernel}_${TFENV_ARCH}" 142 | 143 | keybase_bin="$(command -v keybase 2>/dev/null)"; 144 | shasum_bin="$(command -v shasum 2>/dev/null)"; 145 | sha256sum_bin="$(command -v sha256sum 2>/dev/null)"; 146 | 147 | TFENV_REMOTE="${TFENV_REMOTE:-https://releases.hashicorp.com}"; 148 | version_url="${TFENV_REMOTE}/terraform/${version}"; 149 | 150 | # Thanks for the inconsistency in 0.12-alpha, Hashicorp(!) 151 | if [[ "${version}" =~ 0.12.0-alpha[3-9] ]]; then 152 | tarball_name="terraform_${version}_terraform_${version}_${os}.zip"; 153 | else 154 | tarball_name="terraform_${version}_${os}.zip"; 155 | fi; 156 | 157 | shasums_name="terraform_${version}_SHA256SUMS"; 158 | shasums_signing_key_postfix=".72D7468F"; 159 | shasums_sig="${shasums_name}${shasums_signing_key_postfix}.sig"; 160 | 161 | log 'info' "Installing Terraform v${version}"; 162 | 163 | # Create a local temporary directory for downloads 164 | tmpdir_arg="-t"; 165 | 166 | if mktemp --help 2>&1 | grep -- '--tmpdir' >/dev/null; then 167 | tmpdir_arg="--tmpdir"; 168 | fi; 169 | 170 | download_tmp="$(mktemp -d ${tmpdir_arg} tfenv_download.XXXXXX)" || log 'error' "Unable to create temporary download directory (mktemp -d ${tmpdir_arg} tfenv_download.XXXXXX). Working Directory is: $(pwd)"; 171 | 172 | # Clean it up in case of error 173 | trap "rm -rf ${download_tmp}" EXIT; 174 | 175 | declare curl_progress=""; 176 | case "${TFENV_CURL_OUTPUT:-2}" in 177 | '2') 178 | log 'debug' 'Setting curl progress bar with "-#"'; 179 | curl_progress="-#"; 180 | ;; 181 | '1') 182 | log 'debug' 'Using default curl output'; 183 | curl_progress=""; 184 | ;; 185 | '0') 186 | log 'debug' 'Running curl silently with "-s"'; 187 | curl_progress="-s"; 188 | ;; 189 | *) 190 | log 'error' 'TFENV_CURL_OUTPUT specified, but not with a supported value ([0,1,2])'; 191 | ;; 192 | esac; 193 | 194 | log 'info' "Downloading release tarball from ${version_url}/${tarball_name}"; 195 | curlw ${curl_progress} -f -L -o "${download_tmp}/${tarball_name}" "${version_url}/${tarball_name}" || log 'error' 'Tarball download failed'; 196 | log 'info' "Downloading SHA hash file from ${version_url}/${shasums_name}"; 197 | curlw -s -f -L -o "${download_tmp}/${shasums_name}" "${version_url}/${shasums_name}" || log 'error' 'SHA256 hashes download failed'; 198 | 199 | download_signature() { 200 | log 'info' "Downloading SHA hash signature file from ${version_url}/${shasums_sig}"; 201 | curlw -s -f -L \ 202 | -o "${download_tmp}/${shasums_sig}" \ 203 | "${version_url}/${shasums_sig}" \ 204 | && log 'debug' "SHA256SUMS signature file downloaded successfully to ${download_tmp}/${shasums_sig}" \ 205 | || log 'error' 'SHA256SUMS signature download failed'; 206 | }; 207 | 208 | # If on MacOS with Homebrew, use GNU grep 209 | # This allows keybase login detection to work on Mac, 210 | # and is required to be able to detect terraform version 211 | # from "required_version" setting in "*.tf" files 212 | check_dependencies; 213 | 214 | # Verify signature if verification mechanism (keybase, gpg, etc) is present 215 | if [[ -f "${TFENV_CONFIG_DIR}/use-gnupg" ]]; then 216 | # GnuPG uses the user's keyring, and any web-of-trust or local signatures or 217 | # anything else they have setup. This is the crazy-powerful mode which is 218 | # overly confusing to newcomers. We don't support it without the user creating 219 | # the file use-gnupg, optionally with directives in it. 220 | gnupg_command="$(sed -E -n -e 's/^binary: *//p' <"${TFENV_CONFIG_DIR}/use-gnupg")"; 221 | [[ -n "${gnupg_command}" ]] || gnupg_command=gpg; 222 | 223 | download_signature; 224 | # Deliberately unquoted command, in case caller has something fancier in "use-gnupg". 225 | # Also, don't use batch mode. If someone specifies GnuPG, let them deal with any prompting. 226 | ${gnupg_command} \ 227 | --verify "${download_tmp}/${shasums_sig}" \ 228 | "${download_tmp}/${shasums_name}" \ 229 | || log 'error' 'PGP signature rejected by GnuPG'; 230 | 231 | elif [[ -f "${TFENV_CONFIG_DIR}/use-gpgv" ]]; then 232 | # gpgv is a much simpler interface to verification, but does require that the 233 | # key have been downloaded and marked trusted. 234 | # We don't force the caller to trust the tfenv repo's copy of their key, they 235 | # have to choose to make that trust decision. 236 | gpgv_command="$(sed -E -n -e 's/^binary: *//p' <"${TFENV_CONFIG_DIR}/use-gpgv")"; 237 | trust_tfenv="$(sed -E -n -e 's/^trust.?tfenv: *//p' <"${TFENV_CONFIG_DIR}/use-gpgv")"; 238 | [[ -n "${gpgv_command}" ]] || gpgv_command=gpgv; 239 | 240 | download_signature; 241 | if [[ "${trust_tfenv}" == 'yes' ]]; then 242 | ${gpgv_command} \ 243 | --keyring "${TFENV_ROOT}/share/hashicorp-keys.pgp" \ 244 | "${download_tmp}/${shasums_sig}" \ 245 | "${download_tmp}/${shasums_name}" \ 246 | || log 'error' 'PGP signature rejected'; 247 | else 248 | ${gpgv_command} \ 249 | "${download_tmp}/${shasums_sig}" \ 250 | "${download_tmp}/${shasums_name}" \ 251 | || log 'error' 'PGP signature rejected'; 252 | fi; 253 | elif [[ -n "${keybase_bin}" && -x "${keybase_bin}" ]]; then 254 | grep -Eq '^Logged in:[[:space:]]*yes' <("${keybase_bin}" status); 255 | keybase_logged_in="${?}"; 256 | grep -Fq hashicorp <("${keybase_bin}" list-following); 257 | keybase_following_hc="${?}"; 258 | 259 | if [[ "${keybase_logged_in}" -ne 0 || "${keybase_following_hc}" -ne 0 ]]; then 260 | log 'warn' 'Unable to verify OpenPGP signature unless logged into keybase and following hashicorp'; 261 | else 262 | download_signature; 263 | "${keybase_bin}" pgp verify \ 264 | -S hashicorp \ 265 | -d "${download_tmp}/${shasums_sig}" \ 266 | -i "${download_tmp}/${shasums_name}" \ 267 | && log 'debug' 'SHA256SUMS signature matched' \ 268 | || log 'error' 'SHA256SUMS signature does not match!'; 269 | fi; 270 | else 271 | # Warning about this avoids an unwarranted sense of confidence in the SHA check 272 | log 'warn' "Not instructed to use Local PGP (${TFENV_CONFIG_DIR}/use-{gpgv,gnupg}) & No keybase install found, skipping OpenPGP signature verification"; 273 | fi; 274 | 275 | if [[ -n "${shasum_bin}" && -x "${shasum_bin}" ]]; then 276 | ( 277 | cd "${download_tmp}"; 278 | "${shasum_bin}" \ 279 | -a 256 \ 280 | -s \ 281 | -c <(grep -F "${tarball_name}" "${shasums_name}") 282 | ) || log 'error' 'SHA256 hash does not match!'; 283 | elif [[ -n "${sha256sum_bin}" && -x "${sha256sum_bin}" ]]; then 284 | ( 285 | cd "${download_tmp}"; 286 | "${sha256sum_bin}" \ 287 | -c <(grep -F "${tarball_name}" "${shasums_name}") 288 | ) || log 'error' 'SHA256 hash does not match!'; 289 | else 290 | # Lack of shasum deserves a proper warning 291 | log 'warn' 'No shasum tool available. Skipping SHA256 hash validation'; 292 | fi; 293 | 294 | mkdir -p "${dst_path}" || log 'error' "Failed to make directory ${dst_path}"; 295 | 296 | declare unzip_output; 297 | unzip_output="$(unzip -o "${download_tmp}/${tarball_name}" -d "${dst_path}")" || log 'error' 'Tarball unzip failed'; 298 | while IFS= read -r unzip_line; do 299 | log 'info' "${unzip_line}"; 300 | done < <(printf '%s\n' "${unzip_output}"); 301 | 302 | log 'info' "Installation of terraform v${version} successful. To make this your default version, run 'tfenv use ${version}'"; 303 | -------------------------------------------------------------------------------- /libexec/tfenv-list: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | # Ensure libexec and bin are in $PATH 48 | for dir in libexec bin; do 49 | case ":${PATH}:" in 50 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 51 | *) 52 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 53 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 54 | ;; 55 | esac; 56 | done; 57 | 58 | ##################### 59 | # Begin Script Body # 60 | ##################### 61 | 62 | [ "${#}" -ne 0 ] \ 63 | && log 'error' "usage: tfenv list"; 64 | 65 | [ -d "${TFENV_CONFIG_DIR}/versions" ] \ 66 | || log 'error' 'No versions available. Please install one with: tfenv install'; 67 | 68 | [[ -x "${TFENV_CONFIG_DIR}/versions" && -r "${TFENV_CONFIG_DIR}/versions" ]] \ 69 | || log 'error' "tfenv versions directory is inaccessible: ${TFENV_CONFIG_DIR}/versions"; 70 | 71 | version_name="$(tfenv-version-name 2>/dev/null || true)" \ 72 | && log 'debug' "tfenv-version-name reported: ${version_name}"; 73 | export version_name; 74 | 75 | if [ -z "${TFENV_TERRAFORM_VERSION:-""}" ]; then 76 | version_source="$(tfenv-version-file)" \ 77 | && log 'debug' "tfenv-version-file reported: ${version_source}" \ 78 | || log 'error' "tfenv-version-file failed"; 79 | else 80 | version_source='TFENV_TERRAFORM_VERSION'; 81 | fi; 82 | export version_source; 83 | 84 | # Register for whether a default terraform version has yet been set 85 | declare -i default_set=0; 86 | 87 | print_version () { 88 | if [ "${1}" == "${version_name}" ]; then 89 | echo "* ${1} (set by ${version_source})"; 90 | default_set=1; 91 | else 92 | echo " ${1}"; 93 | fi; 94 | }; 95 | 96 | log 'debug' 'Listing versions...'; 97 | local_versions=($(\find "${TFENV_CONFIG_DIR}/versions/" -type d -exec basename {} \; \ 98 | | tail -n +2 \ 99 | | sort -t'.' -k 1nr,1 -k 2nr,2 -k 3nr,3)); 100 | 101 | log 'debug' "Local versions: ${local_versions[@]}"; 102 | 103 | log 'debug' 'Printing versions...'; 104 | for local_version in ${local_versions[@]}; do 105 | print_version "${local_version}"; 106 | done; 107 | 108 | [ "${default_set}" -eq 0 ] && log 'info' "No default set. Set with 'tfenv use '"; 109 | 110 | exit 0; 111 | -------------------------------------------------------------------------------- /libexec/tfenv-list-remote: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname "${target_file}")" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | # Ensure libexec and bin are in $PATH 48 | for dir in libexec bin; do 49 | case ":${PATH}:" in 50 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 51 | *) 52 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 53 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 54 | ;; 55 | esac; 56 | done; 57 | 58 | ##################### 59 | # Begin Script Body # 60 | ##################### 61 | 62 | if [ "${#}" -ne 0 ];then 63 | echo "usage: tfenv list-remote" 1>&2; 64 | exit 1; 65 | fi; 66 | 67 | TFENV_REMOTE="${TFENV_REMOTE:-https://releases.hashicorp.com}"; 68 | log 'debug' "TFENV_REMOTE: ${TFENV_REMOTE}"; 69 | 70 | declare remote_versions; 71 | remote_versions="$(curlw -sSf "${TFENV_REMOTE}/terraform/")" \ 72 | || log 'error' "Failed to download remote versions from ${TFENV_REMOTE}/terraform/"; 73 | 74 | #log 'debug' "Remote versions available: ${remote_versions}"; # Even in debug mode this is too verbose 75 | 76 | if [[ "${TFENV_REVERSE_REMOTE:-0}" -eq 1 ]]; then 77 | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta|alpha|oci)-?[0-9]*)?" <<<"${remote_versions}" | uniq | awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }'; 78 | else 79 | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta|alpha|oci)-?[0-9]*)?" <<<"${remote_versions}" | uniq; 80 | fi; 81 | -------------------------------------------------------------------------------- /libexec/tfenv-min-required: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: tfenv min-required 3 | # Summary: Detect the minimal required version from *tf files 4 | 5 | set -uo pipefail; 6 | 7 | #################################### 8 | # Ensure we can execute standalone # 9 | #################################### 10 | 11 | function early_death() { 12 | echo "[FATAL] ${0}: ${1}" >&2; 13 | exit 1; 14 | }; 15 | 16 | if [ -z "${TFENV_ROOT:-""}" ]; then 17 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 18 | readlink_f() { 19 | local target_file="${1}"; 20 | local file_name; 21 | 22 | while [ "${target_file}" != "" ]; do 23 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 24 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 25 | target_file="$(readlink "${file_name}")"; 26 | done; 27 | 28 | echo "$(pwd -P)/${file_name}"; 29 | }; 30 | 31 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 32 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 33 | else 34 | TFENV_ROOT="${TFENV_ROOT%/}"; 35 | fi; 36 | export TFENV_ROOT; 37 | 38 | if [ -n "${TFENV_HELPERS:-""}" ]; then 39 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 40 | else 41 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 42 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 43 | log 'debug' 'Helpers sourced successfully'; 44 | else 45 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 46 | fi; 47 | fi; 48 | 49 | # Ensure libexec and bin are in $PATH 50 | for dir in libexec bin; do 51 | case ":${PATH}:" in 52 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 53 | *) 54 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 55 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 56 | ;; 57 | esac; 58 | done; 59 | 60 | ##################### 61 | # Begin Script Body # 62 | ##################### 63 | 64 | bailout() { 65 | log 'error' 'Error: Could not determine required_version based on your terraform sources. 66 | Make sure at least one of your *tf or *.tf.json files includes a required version section like 67 | terraform { 68 | required_version = ">= 0.0.0" 69 | } 70 | 71 | see https://developer.hashicorp.com/terraform/language/settings for details'; 72 | }; 73 | 74 | declare min_required="$(tfenv-min-required "${TFENV_DIR:-$(pwd)}")"; 75 | [[ -n "${min_required}" ]] \ 76 | && echo "${min_required}" \ 77 | || bailout; 78 | -------------------------------------------------------------------------------- /libexec/tfenv-pin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | # Ensure libexec and bin are in $PATH 48 | for dir in libexec bin; do 49 | case ":${PATH}:" in 50 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 51 | *) 52 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 53 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 54 | ;; 55 | esac; 56 | done; 57 | 58 | ##################### 59 | # Begin Script Body # 60 | ##################### 61 | 62 | [ "${#}" -ne 0 ] \ 63 | && log 'error' "usage: tfenv pin"; 64 | 65 | [ -d "${TFENV_CONFIG_DIR}/versions/" ] \ 66 | || log 'error' 'No versions available. Please install one with: tfenv install'; 67 | 68 | [[ -x "${TFENV_CONFIG_DIR}/versions" && -r "${TFENV_CONFIG_DIR}/versions" ]] \ 69 | || log 'error' "tfenv versions directory is inaccessible: ${TFENV_CONFIG_DIR}/versions"; 70 | 71 | version_name="$(tfenv-version-name 2>/dev/null || true)" \ 72 | && log 'debug' "tfenv-version-name reported: ${version_name}"; 73 | 74 | echo "${version_name}" > .terraform-version; 75 | log 'info' "Pinned version by writing \"${version_name}\" to $(pwd)/.terraform-version"; 76 | 77 | exit 0; 78 | -------------------------------------------------------------------------------- /libexec/tfenv-resolve-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: tfenv resolve-version [] 3 | # Summary: Resolve the version to action based on the environment and optional input token 4 | 5 | set -uo pipefail; 6 | 7 | #################################### 8 | # Ensure we can execute standalone # 9 | #################################### 10 | 11 | function early_death() { 12 | echo "[FATAL] ${0}: ${1}" >&2; 13 | exit 1; 14 | }; 15 | 16 | if [ -z "${TFENV_ROOT:-""}" ]; then 17 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 18 | readlink_f() { 19 | local target_file="${1}"; 20 | local file_name; 21 | 22 | while [ "${target_file}" != "" ]; do 23 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 24 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 25 | target_file="$(readlink "${file_name}")"; 26 | done; 27 | 28 | echo "$(pwd -P)/${file_name}"; 29 | }; 30 | 31 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 32 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 33 | else 34 | TFENV_ROOT="${TFENV_ROOT%/}"; 35 | fi; 36 | export TFENV_ROOT; 37 | 38 | if [ -n "${TFENV_HELPERS:-""}" ]; then 39 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 40 | else 41 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 42 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 43 | log 'debug' 'Helpers sourced successfully'; 44 | else 45 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 46 | fi; 47 | fi; 48 | 49 | # Ensure libexec and bin are in $PATH 50 | for dir in libexec bin; do 51 | case ":${PATH}:" in 52 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 53 | *) 54 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 55 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 56 | ;; 57 | esac; 58 | done; 59 | 60 | # If on MacOS with Homebrew, use GNU grep 61 | # This allows keybase login detection to work on Mac, 62 | # and is required to be able to detect terraform version 63 | # from "required_version" setting in "*.tf" files 64 | check_dependencies; 65 | 66 | ##################### 67 | # Begin Script Body # 68 | ##################### 69 | 70 | declare version_requested version regex min_required version_file; 71 | 72 | declare arg="${1:-""}"; 73 | 74 | if [ -z "${arg}" -a -z "${TFENV_TERRAFORM_VERSION:-""}" ]; then 75 | version_file="$(tfenv-version-file)"; 76 | log 'debug' "Version File: ${version_file}"; 77 | 78 | if [ "${version_file}" != "${TFENV_CONFIG_DIR}/version" ]; then 79 | log 'debug' "Version File (${version_file}) is not the default \${TFENV_CONFIG_DIR}/version (${TFENV_CONFIG_DIR}/version)"; 80 | version_requested="$(cat "${version_file}")" \ 81 | || log 'error' "Failed to open ${version_file}"; 82 | 83 | elif [ -f "${version_file}" ]; then 84 | log 'debug' "Version File is the default \${TFENV_CONFIG_DIR}/version (${TFENV_CONFIG_DIR}/version)"; 85 | version_requested="$(cat "${version_file}")" \ 86 | || log 'error' "Failed to open ${version_file}"; 87 | 88 | # Absolute fallback 89 | if [ -z "${version_requested}" ]; then 90 | log 'debug' 'Version file had no content. Falling back to "latest"'; 91 | version_requested='latest'; 92 | fi; 93 | 94 | else 95 | log 'debug' "Version File is the default \${TFENV_CONFIG_DIR}/version (${TFENV_CONFIG_DIR}/version) but it doesn't exist"; 96 | log 'debug' 'No version requested on the command line or in the version file search path. Installing "latest"'; 97 | version_requested='latest'; 98 | fi; 99 | elif [ -n "${TFENV_TERRAFORM_VERSION:-""}" ]; then 100 | version_requested="${TFENV_TERRAFORM_VERSION}"; 101 | log 'debug' "TFENV_TERRAFORM_VERSION is set: ${TFENV_TERRAFORM_VERSION}"; 102 | else 103 | version_requested="${arg}"; 104 | fi; 105 | 106 | [[ -n "${version_requested:-""}" ]] \ 107 | && log 'debug' "Version Requested: ${version_requested}" \ 108 | || log 'error' 'Version could not be resolved!'; 109 | 110 | # Accept a v-prefixed version, but strip the v. 111 | if [[ "${version_requested}" =~ ^v.*$ ]]; then 112 | log 'debug' "Version Requested is prefixed with a v. Stripping the v."; 113 | version_requested="${version_requested#v*}"; 114 | fi; 115 | 116 | if [[ "${version_requested}" =~ ^min-required$ ]]; then 117 | log 'debug' 'Detecting minimum required version...'; 118 | min_required="$(tfenv-min-required)" \ 119 | || log 'error' 'tfenv-min-required failed'; 120 | 121 | if [ -z "${min_required}" ]; then 122 | log 'debug' 'It was not possible to detect a minimum-required version. Do you have a required_version line present?'; 123 | exit; 124 | fi; 125 | 126 | log 'debug' "Minimum required version detected: ${min_required}"; 127 | version_requested="${min_required}"; 128 | fi; 129 | 130 | if [[ "${version_requested}" =~ ^latest-allowed$ ]]; then 131 | log 'debug' 'Detecting latest allowable version...'; 132 | version_spec="$(grep -h required_version "${TFENV_DIR:-$(pwd)}"/{*.tf,*.tf.json} 2>/dev/null | { IFS='"' read -r _ ver _; echo "${ver%%,*}"; })"; 133 | version_num="$(echo "${version_spec}" | sed -E 's/[^0-9.]+//')"; 134 | log 'debug' "Using ${version_num} from version spec: ${version_spec}"; 135 | 136 | case "${version_spec}" in 137 | '>'*) 138 | version_requested=latest; 139 | ;; 140 | '<='*) 141 | version_requested="${version_num}"; 142 | ;; 143 | '~>'*) 144 | version_without_rightmost="${version_num%.*}"; 145 | version_requested="latest:^${version_without_rightmost}"; 146 | ;; 147 | *) 148 | log 'error' "Unsupported version spec: '${version_spec}', only >, >=, <=, and ~> are supported."; 149 | ;; 150 | esac; 151 | log 'debug' "Determined the requested version to be: ${version_requested}"; 152 | fi; 153 | 154 | if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then 155 | version="${version_requested%%\:*}"; 156 | regex="${version_requested##*\:}"; 157 | log 'debug' "Version uses latest keyword with regex: ${regex}"; 158 | elif [[ "${version_requested}" =~ ^latest$ ]]; then 159 | version="${version_requested}"; 160 | regex="^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 161 | log 'debug' "Version uses latest keyword alone. Forcing regex to match stable versions only: ${regex}"; 162 | else 163 | version="${version_requested}"; 164 | regex="^${version_requested}$"; 165 | log 'debug' "Version is explicit: ${version}. Regex enforces the version: ${regex}"; 166 | fi; 167 | 168 | echo "${version}:${regex}"; 169 | -------------------------------------------------------------------------------- /libexec/tfenv-uninstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | # Ensure libexec and bin are in $PATH 48 | for dir in libexec bin; do 49 | case ":${PATH}:" in 50 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 51 | *) 52 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 53 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 54 | ;; 55 | esac; 56 | done; 57 | 58 | ##################### 59 | # Begin Script Body # 60 | ##################### 61 | 62 | [ "${#}" -gt 1 ] && log 'error' 'usage: tfenv uninstall []'; 63 | 64 | declare version_requested version regex; 65 | declare arg="${1:-""}"; 66 | 67 | if [ -z "${arg:-""}" -a -z "${TFENV_TERRAFORM_VERSION:-""}" ]; then 68 | version_file="$(tfenv-version-file)"; 69 | log 'debug' "Version File: ${version_file}"; 70 | if [ "${version_file}" != "${TFENV_CONFIG_DIR}/version" ]; then 71 | log 'debug' "Version File (${version_file}) is not the default \${TFENV_CONFIG_DIR}/version (${TFENV_CONFIG_DIR}/version)"; 72 | version_requested="$(cat "${version_file}")" \ 73 | || log 'error' "Failed to open ${version_file}"; 74 | elif [ -f "${version_file}" ]; then 75 | log 'debug' "Version File is the default \${TFENV_CONFIG_DIR}/version (${TFENV_CONFIG_DIR}/version)"; 76 | version_requested="$(cat "${version_file}")" \ 77 | || log 'error' "Failed to open ${version_file}"; 78 | else 79 | log 'debug' "Version File is the default \${TFENV_CONFIG_DIR}/version (${TFENV_CONFIG_DIR}/version) but it doesn't exist"; 80 | log 'info' 'No version requested on the command line or in the version file search path. Installing "latest"'; 81 | version_requested='latest'; 82 | fi; 83 | elif [ -n "${TFENV_TERRAFORM_VERSION:-""}" ]; then 84 | version_requested="${TFENV_TERRAFORM_VERSION}"; 85 | log 'debug' "TFENV_TERRAFORM_VERSION is set: ${TFENV_TERRAFORM_VERSION}"; 86 | else 87 | version_requested="${arg}"; 88 | fi; 89 | 90 | log 'debug' "Version Requested: ${version_requested}"; 91 | 92 | if [[ "${version_requested}" =~ ^min-required$ ]]; then 93 | log 'error' 'min-required is an unsupported option for uninstall'; 94 | fi; 95 | 96 | if [[ "${version_requested}" == latest-allowed ]]; then 97 | log 'error' 'latest-allowed is an unsupported option for uninstall'; 98 | fi; 99 | 100 | if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then 101 | version="${version_requested%%\:*}"; 102 | regex="${version_requested##*\:}"; 103 | elif [[ "${version_requested}" =~ ^latest$ ]]; then 104 | version="${version_requested}"; 105 | regex=""; 106 | else 107 | version="${version_requested}"; 108 | regex="^${version_requested}$"; 109 | fi; 110 | 111 | [ -z "${version:-""}" ] && log 'error' "Version not specified on the command line or on version file search path."; 112 | 113 | log 'debug' "Processing uninstall for version ${version}, using regex ${regex}"; 114 | 115 | version="$(tfenv-list | sed -E 's/^(\*| )? //g; s/ \(set by .+\)$//' | grep -e "${regex}" | head -n 1)"; 116 | [ -n "${version}" ] || log 'error' "No versions matching '${regex}' found in local"; 117 | 118 | dst_path="${TFENV_CONFIG_DIR}/versions/${version}"; 119 | if [ -f "${dst_path}/terraform" ]; then 120 | log 'info' "Uninstall Terraform v${version}"; 121 | rm -r "${dst_path}"; 122 | 123 | # If no versions remain, remove the versions directory 124 | rmdir "${TFENV_CONFIG_DIR}/versions" 2>/dev/null; 125 | 126 | log 'info' "Terraform v${version} is successfully uninstalled"; 127 | fi; 128 | -------------------------------------------------------------------------------- /libexec/tfenv-use: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | #################################### 5 | # Ensure we can execute standalone # 6 | #################################### 7 | 8 | function early_death() { 9 | echo "[FATAL] ${0}: ${1}" >&2; 10 | exit 1; 11 | }; 12 | 13 | if [ -z "${TFENV_ROOT:-""}" ]; then 14 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 15 | readlink_f() { 16 | local target_file="${1}"; 17 | local file_name; 18 | 19 | while [ "${target_file}" != "" ]; do 20 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 21 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 22 | target_file="$(readlink "${file_name}")"; 23 | done; 24 | 25 | echo "$(pwd -P)/${file_name}"; 26 | }; 27 | 28 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 29 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 30 | else 31 | TFENV_ROOT="${TFENV_ROOT%/}"; 32 | fi; 33 | export TFENV_ROOT; 34 | 35 | if [ -n "${TFENV_HELPERS:-""}" ]; then 36 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 37 | else 38 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 39 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 40 | log 'debug' 'Helpers sourced successfully'; 41 | else 42 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 43 | fi; 44 | fi; 45 | 46 | # Ensure libexec and bin are in $PATH 47 | for dir in libexec bin; do 48 | case ":${PATH}:" in 49 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 50 | *) 51 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 52 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 53 | ;; 54 | esac; 55 | done; 56 | 57 | ##################### 58 | # Begin Script Body # 59 | ##################### 60 | 61 | [ "${#}" -gt 1 ] && log 'error' 'usage: tfenv use []'; 62 | 63 | declare auto_install="${TFENV_AUTO_INSTALL:-true}"; 64 | 65 | if [ "${auto_install}" != 'true' ]; then 66 | [ -d "${TFENV_CONFIG_DIR}/versions" ] \ 67 | || log 'error' 'No versions of terraform installed. Please install one with: tfenv install'; 68 | fi; 69 | 70 | declare requested_arg="${1:-""}"; 71 | declare version_source_suffix=""; 72 | declare requested="${requested_arg}"; 73 | declare loaded_version_file="$(tfenv-version-file)"; 74 | 75 | if [ -z "${requested_arg}" -a -z "${TFENV_TERRAFORM_VERSION:-""}" ]; then 76 | version_source_suffix=" (set by ${loaded_version_file})"; 77 | 78 | if [ -f "${loaded_version_file}" ]; then 79 | requested="$(cat "${loaded_version_file}" || true)"; 80 | else 81 | # No-one asked for anything, no default version is set either 82 | requested='latest'; 83 | fi; 84 | 85 | elif [ -z "${requested_arg}" ]; then 86 | version_source_suffix=' (set by TFENV_TERRAFORM_VERSION)'; 87 | requested="${TFENV_TERRAFORM_VERSION}"; 88 | fi; 89 | 90 | log debug "Resolving version with: tfenv-resolve-version ${requested_arg}"; 91 | declare resolved="$(tfenv-resolve-version ${requested_arg})"; 92 | [ -z "${resolved}" ] && log 'error' "Failure to resolve version from ${requested_arg}"; 93 | 94 | log debug "Resolved to: ${resolved}"; 95 | 96 | declare version="${resolved%%\:*}"; 97 | declare regex="${resolved##*\:}"; 98 | declare installed_version=''; 99 | 100 | log 'debug' "Searching ${TFENV_CONFIG_DIR}/versions/ for latest version matching ${regex}"; 101 | 102 | if [ -d "${TFENV_CONFIG_DIR}/versions" ]; then 103 | installed_version="$(\find "${TFENV_CONFIG_DIR}/versions/" -type d -exec basename {} \; \ 104 | | tail -n +2 \ 105 | | grep -e "${regex}" \ 106 | | sort -t'.' -k 1nr,1 -k 2nr,2 -k 3r,3 \ 107 | | head -n 1 108 | )"; 109 | fi; 110 | 111 | if [ -n "${installed_version}" ]; then 112 | log 'debug' "Found version: ${installed_version}"; 113 | else 114 | if [ "${auto_install}" == 'true' ]; then 115 | log 'info' "No installed versions of terraform matched '${resolved}'. Trying to install a matching version since TFENV_AUTO_INSTALL=true"; 116 | 117 | declare install_version=''; 118 | [ "${version}" == 'latest' ] && install_version="${version}:${regex}" || install_version="${version}"; 119 | 120 | tfenv-install "${install_version}" || log 'error' 'Installing a matching version failed'; 121 | 122 | installed_version="$(\find "${TFENV_CONFIG_DIR}/versions/" -type d -exec basename {} \; \ 123 | | tail -n +2 \ 124 | | sort -t'.' -k 1nr,1 -k 2nr,2 -k 3nr,3 \ 125 | | grep -e "${regex}" \ 126 | | head -n 1 127 | )"; 128 | 129 | [ -n "${installed_version}" ] \ 130 | || log 'error' "Despite successfully installing a version matching '${resolved}', a matching version could not be found in '${TFENV_CONFIG_DIR}/versions/' - This should be pretty much impossible"; 131 | fi; 132 | fi; 133 | 134 | target_path="${TFENV_CONFIG_DIR}/versions/${installed_version}"; 135 | log 'debug' "target_path is ${TFENV_CONFIG_DIR}/versions/${installed_version}"; 136 | 137 | [ -d "${target_path}" ] \ 138 | || log 'error' "Version directory for ${installed_version} (${target_path}) is not present! Manual intervention required."; 139 | [ -f "${target_path}/terraform" ] \ 140 | || log 'error' "Version directory for ${installed_version} is present, but the terraform binary is not! Manual intervention required."; 141 | [ -x "${target_path}/terraform" ] \ 142 | || log 'error' "Version directory for ${installed_version} is present, but the terraform binary is not executable! Manual intervention required."; 143 | 144 | log 'info' "Switching default version to v${installed_version}"; 145 | version_file="${TFENV_CONFIG_DIR}/version"; 146 | log 'debug' "Writing \"${installed_version}\" to \"${version_file}\""; 147 | echo "${installed_version}" > "${version_file}" \ 148 | || log 'error' "Switch to v${installed_version} failed"; 149 | 150 | if [ "${version_file}" != "${loaded_version_file}" ]; then 151 | log 'warn' "Default version file overridden by ${loaded_version_file}, changing the default version has no effect"; 152 | fi; 153 | 154 | log 'info' "Default version (when not overridden by .terraform-version or TFENV_TERRAFORM_VERSION) is now: ${installed_version}"; 155 | -------------------------------------------------------------------------------- /libexec/tfenv-version-file: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: tfenv version-file 3 | # Summary: Detect the file that sets the current tfenv version 4 | 5 | set -uo pipefail; 6 | 7 | #################################### 8 | # Ensure we can execute standalone # 9 | #################################### 10 | 11 | function early_death() { 12 | echo "[FATAL] ${0}: ${1}" >&2; 13 | exit 1; 14 | }; 15 | 16 | if [ -z "${TFENV_ROOT:-""}" ]; then 17 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 18 | readlink_f() { 19 | local target_file="${1}"; 20 | local file_name; 21 | 22 | while [ "${target_file}" != "" ]; do 23 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 24 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 25 | target_file="$(readlink "${file_name}")"; 26 | done; 27 | 28 | echo "$(pwd -P)/${file_name}"; 29 | }; 30 | 31 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 32 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 33 | else 34 | TFENV_ROOT="${TFENV_ROOT%/}"; 35 | fi; 36 | export TFENV_ROOT; 37 | 38 | if [ -n "${TFENV_HELPERS:-""}" ]; then 39 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 40 | else 41 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 42 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 43 | log 'debug' 'Helpers sourced successfully'; 44 | else 45 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 46 | fi; 47 | fi; 48 | 49 | # Ensure libexec and bin are in $PATH 50 | for dir in libexec bin; do 51 | case ":${PATH}:" in 52 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 53 | *) 54 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 55 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 56 | ;; 57 | esac; 58 | done; 59 | 60 | ##################### 61 | # Begin Script Body # 62 | ##################### 63 | 64 | tfenv-version-file "$@"; 65 | -------------------------------------------------------------------------------- /libexec/tfenv-version-name: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: Show the currently-selected terraform version 3 | 4 | set -uo pipefail; 5 | 6 | #################################### 7 | # Ensure we can execute standalone # 8 | #################################### 9 | 10 | function early_death() { 11 | echo "[FATAL] ${0}: ${1}" >&2; 12 | exit 1; 13 | }; 14 | 15 | if [ -z "${TFENV_ROOT:-""}" ]; then 16 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 17 | readlink_f() { 18 | local target_file="${1}"; 19 | local file_name; 20 | 21 | while [ "${target_file}" != "" ]; do 22 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 23 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 24 | target_file="$(readlink "${file_name}")"; 25 | done; 26 | 27 | echo "$(pwd -P)/${file_name}"; 28 | }; 29 | 30 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 31 | [ -n "${TFENV_ROOT}" ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 32 | else 33 | TFENV_ROOT="${TFENV_ROOT%/}"; 34 | fi; 35 | export TFENV_ROOT; 36 | 37 | if [ -n "${TFENV_HELPERS:-""}" ]; then 38 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 39 | else 40 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 41 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 42 | log 'debug' 'Helpers sourced successfully'; 43 | else 44 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 45 | fi; 46 | fi; 47 | 48 | # Ensure libexec and bin are in $PATH 49 | for dir in libexec bin; do 50 | case ":${PATH}:" in 51 | *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; 52 | *) 53 | log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; 54 | export PATH="${TFENV_ROOT}/${dir}:${PATH}"; 55 | ;; 56 | esac; 57 | done; 58 | 59 | # If on MacOS with Homebrew, use GNU grep 60 | # This allows keybase login detection to work on Mac, 61 | # and is required to be able to detect terraform version 62 | # from "required_version" setting in "*.tf" files 63 | check_dependencies; 64 | 65 | ##################### 66 | # Begin Script Body # 67 | ##################### 68 | 69 | tfenv-version-name "$@"; 70 | -------------------------------------------------------------------------------- /share/hashicorp-keys.pgp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfutils/tfenv/39d8c27ad9862ffdec57989b66fd2720cb72e76c/share/hashicorp-keys.pgp -------------------------------------------------------------------------------- /test/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | if [[ $(uname) == 'Darwin' ]] && [ $(which brew) ]; then 5 | brew install grep; 6 | fi; 7 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | #################################### 5 | # Ensure we can execute standalone # 6 | #################################### 7 | 8 | function early_death() { 9 | echo "[FATAL] ${0}: ${1}" >&2; 10 | exit 1; 11 | }; 12 | 13 | if [ -z "${TFENV_ROOT:-""}" ]; then 14 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 15 | readlink_f() { 16 | local target_file="${1}"; 17 | local file_name; 18 | 19 | while [ "${target_file}" != "" ]; do 20 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 21 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 22 | target_file="$(readlink "${file_name}")"; 23 | done; 24 | 25 | echo "$(pwd -P)/${file_name}"; 26 | }; 27 | 28 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 29 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 30 | else 31 | TFENV_ROOT="${TFENV_ROOT%/}"; 32 | fi; 33 | export TFENV_ROOT; 34 | 35 | if [ -n "${TFENV_HELPERS:-""}" ]; then 36 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 37 | else 38 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 39 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 40 | log 'debug' 'Helpers sourced successfully'; 41 | else 42 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 43 | fi; 44 | fi; 45 | 46 | ##################### 47 | # Begin Script Body # 48 | ##################### 49 | 50 | export PATH="${TFENV_ROOT}/bin:${PATH}"; 51 | 52 | errors=(); 53 | if [ "${#}" -ne 0 ]; then 54 | targets="$@"; 55 | else 56 | targets="$(\ls "$(dirname "${0}")" | grep 'test_')"; 57 | fi; 58 | 59 | for t in ${targets}; do 60 | bash "$(dirname "${0}")/${t}" \ 61 | || errors+=( "${t}" ); 62 | done; 63 | 64 | if [ "${#errors[@]}" -ne 0 ]; then 65 | log 'warn' '===== The following test suites failed ====='; 66 | for error in "${errors[@]}"; do 67 | log 'warn' "\t${error}"; 68 | done; 69 | log 'error' 'Test suite failure(s)'; 70 | else 71 | log 'info' 'All test suites passed.'; 72 | fi; 73 | 74 | exit 0; 75 | -------------------------------------------------------------------------------- /test/test_install_and_use.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uo pipefail; 3 | 4 | #################################### 5 | # Ensure we can execute standalone # 6 | #################################### 7 | 8 | function early_death() { 9 | echo "[FATAL] ${0}: ${1}" >&2; 10 | exit 1; 11 | }; 12 | 13 | if [ -z "${TFENV_ROOT:-""}" ]; then 14 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 15 | readlink_f() { 16 | local target_file="${1}"; 17 | local file_name; 18 | 19 | while [ "${target_file}" != "" ]; do 20 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 21 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 22 | target_file="$(readlink "${file_name}")"; 23 | done; 24 | 25 | echo "$(pwd -P)/${file_name}"; 26 | }; 27 | 28 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 29 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 30 | else 31 | TFENV_ROOT="${TFENV_ROOT%/}"; 32 | fi; 33 | export TFENV_ROOT; 34 | 35 | if [ -n "${TFENV_HELPERS:-""}" ]; then 36 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 37 | else 38 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 39 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 40 | log 'debug' 'Helpers sourced successfully'; 41 | else 42 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 43 | fi; 44 | fi; 45 | 46 | ##################### 47 | # Begin Script Body # 48 | ##################### 49 | 50 | test_install_and_use() { 51 | # Takes a static version and the optional keyword to install it with 52 | local k="${2-""}"; 53 | local v="${1}"; 54 | tfenv install "${k}" || return 1; 55 | check_installed_version "${v}" || return 1; 56 | tfenv use "${k}" || return 1; 57 | check_active_version "${v}" || return 1; 58 | return 0; 59 | }; 60 | 61 | test_install_and_use_with_env() { 62 | # Takes a static version and the optional keyword to install it with 63 | local k="${2-""}"; 64 | local v="${1}"; 65 | TFENV_TERRAFORM_VERSION="${k}" tfenv install || return 1; 66 | check_installed_version "${v}" || return 1; 67 | TFENV_TERRAFORM_VERSION="${k}" tfenv use || return 1; 68 | TFENV_TERRAFORM_VERSION="${k}" check_active_version "${v}" || return 1; 69 | return 0; 70 | }; 71 | 72 | test_install_and_use_overridden() { 73 | # Takes a static version and the optional keyword to install it with 74 | local k="${2-""}"; 75 | local v="${1}"; 76 | tfenv install "${k}" || return 1; 77 | check_installed_version "${v}" || return 1; 78 | tfenv use "${k}" || return 1; 79 | check_default_version "${v}" || return 1; 80 | return 0; 81 | }; 82 | 83 | declare -a errors=(); 84 | 85 | log 'info' '### Test Suite: Install and Use'; 86 | 87 | tests__desc=( 88 | 'latest version' 89 | 'latest possibly-unstable version' 90 | 'latest alpha' 91 | 'latest beta' 92 | 'latest rc' 93 | 'latest possibly-unstable version from 0.11' 94 | '0.11.15-oci' 95 | 'latest version matching regex' 96 | 'specific version' 97 | 'specific version with v prefix' 98 | ); 99 | 100 | tests__kv=( 101 | "$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | head -n 1),latest" 102 | "$(tfenv list-remote | head -n 1),latest:" 103 | "$(tfenv list-remote | grep 'alpha' | head -n 1),latest:alpha" 104 | "$(tfenv list-remote | grep 'beta' | head -n 1),latest:beta" 105 | "$(tfenv list-remote | grep 'rc' | head -n 1),latest:rc" 106 | "$(tfenv list-remote | grep '^0\.11\.' | head -n 1),latest:^0.11." 107 | '0.11.15-oci,0.11.15-oci' 108 | '1.3.10,latest:^1\.3' 109 | '1.6.3,1.6.3' 110 | '0.14.6,v0.14.6' 111 | ); 112 | 113 | log 'info' "Kernel under test: $(uname -s)"; 114 | 115 | if [[ "$(uname -s)" != Darwin* ]]; then 116 | log 'info' "We're not Darwin! Adding legacy tests."; 117 | tests__desc+=( 118 | 'legacy latest version matching regex' 119 | 'legacy specific version' 120 | ); 121 | 122 | tests__kv+=( 123 | '0.8.8,latest:^0.8' 124 | '0.7.13,0.7.13' 125 | ); 126 | else 127 | log 'warn' "We're Darwin! Skipping legacy tests."; 128 | fi; 129 | 130 | tests_count=${#tests__desc[@]}; 131 | 132 | declare desc kv k v test_num; 133 | 134 | for ((test_iter=0; test_iter<${tests_count}; ++test_iter )) ; do 135 | cleanup || log 'error' 'Cleanup failed?!'; 136 | test_num=$((test_iter + 1)); 137 | desc=${tests__desc[${test_iter}]}; 138 | kv="${tests__kv[${test_iter}]}"; 139 | v="${kv%,*}"; 140 | k="${kv##*,}"; 141 | log 'info' "## Param Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} )"; 142 | test_install_and_use "${v}" "${k}" \ 143 | && log info "## Param Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} ) succeeded" \ 144 | || error_and_proceed "## Param Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} ) failed"; 145 | done; 146 | 147 | for ((test_iter=0; test_iter<${tests_count}; ++test_iter )) ; do 148 | cleanup || log 'error' 'Cleanup failed?!'; 149 | test_num=$((test_iter + 1)); 150 | desc=${tests__desc[${test_iter}]}; 151 | kv="${tests__kv[${test_iter}]}"; 152 | v="${kv%,*}"; 153 | k="${kv##*,}"; 154 | log 'info' "## ./.terraform-version Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} )"; 155 | log 'info' "Writing ${k} to ./.terraform-version"; 156 | echo "${k}" > ./.terraform-version; 157 | test_install_and_use "${v}" \ 158 | && log info "## ./.terraform-version Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} ) succeeded" \ 159 | || error_and_proceed "## ./.terraform-version Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} ) failed"; 160 | done; 161 | 162 | for ((test_iter=0; test_iter<${tests_count}; ++test_iter )) ; do 163 | cleanup || log 'error' 'Cleanup failed?!'; 164 | test_num=$((test_iter + 1)); 165 | desc=${tests__desc[${test_iter}]}; 166 | kv="${tests__kv[${test_iter}]}"; 167 | v="${kv%,*}"; 168 | k="${kv##*,}"; 169 | log 'info' "## TFENV_TERRAFORM_VERSION Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} )"; 170 | log 'info' "Writing 0.0.0 to ./.terraform-version"; 171 | echo "0.0.0" > ./.terraform-version; 172 | test_install_and_use_with_env "${v}" "${k}" \ 173 | && log info "## TFENV_TERRAFORM_VERSION Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} ) succeeded" \ 174 | || error_and_proceed "## TFENV_TERRAFORM_VERSION Test ${test_num}/${tests_count}: ${desc} ( ${k} / ${v} ) failed"; 175 | done; 176 | 177 | cleanup || log 'error' 'Cleanup failed?!'; 178 | log 'info' '## ${HOME}/.terraform-version Test Preparation'; 179 | 180 | # 0.12.22 reports itself as 0.12.21 and breaks testing 181 | declare v1="$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | grep -v '0.12.22' | head -n 2 | tail -n 1)"; 182 | declare v2="$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | grep -v '0.12.22' | head -n 1)"; 183 | 184 | if [ -f "${HOME}/.terraform-version" ]; then 185 | log 'info' "Backing up ${HOME}/.terraform-version to ${HOME}/.terraform-version.bup"; 186 | mv "${HOME}/.terraform-version" "${HOME}/.terraform-version.bup"; 187 | fi; 188 | log 'info' "Writing ${v1} to ${HOME}/.terraform-version"; 189 | echo "${v1}" > "${HOME}/.terraform-version"; 190 | 191 | log 'info' "## \${HOME}/.terraform-version Test 1/3: Install and Use ( ${v1} )"; 192 | test_install_and_use "${v1}" \ 193 | && log info "## \${HOME}/.terraform-version Test 1/1: ( ${v1} ) succeeded" \ 194 | || error_and_proceed "## \${HOME}/.terraform-version Test 1/1: ( ${v1} ) failed"; 195 | 196 | log 'info' "## \${HOME}/.terraform-version Test 2/3: Override Install with Parameter ( ${v2} )"; 197 | test_install_and_use_overridden "${v2}" "${v2}" \ 198 | && log info "## \${HOME}/.terraform-version Test 2/3: ( ${v2} ) succeeded" \ 199 | || error_and_proceed "## \${HOME}/.terraform-version Test 2/3: ( ${v2} ) failed"; 200 | 201 | log 'info' "## \${HOME}/.terraform-version Test 3/3: Override Use with Parameter ( ${v2} )"; 202 | ( 203 | tfenv use "${v2}" || exit 1; 204 | check_default_version "${v2}" || exit 1; 205 | ) && log info "## \${HOME}/.terraform-version Test 3/3: ( ${v2} ) succeeded" \ 206 | || error_and_proceed "## \${HOME}/.terraform-version Test 3/3: ( ${v2} ) failed"; 207 | 208 | log 'info' '## \${HOME}/.terraform-version Test Cleanup'; 209 | log 'info' "Deleting ${HOME}/.terraform-version"; 210 | rm "${HOME}/.terraform-version"; 211 | if [ -f "${HOME}/.terraform-version.bup" ]; then 212 | log 'info' "Restoring backup from ${HOME}/.terraform-version.bup to ${HOME}/.terraform-version"; 213 | mv "${HOME}/.terraform-version.bup" "${HOME}/.terraform-version"; 214 | fi; 215 | 216 | log 'info' '## Use Auto-Install Test 1/2: (No Input)'; 217 | cleanup || log 'error' 'Cleanup failed?!'; 218 | 219 | ( 220 | tfenv use || exit 1; 221 | check_default_version "$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | head -n 1)" || exit 1; 222 | ) && log info '## Use Auto-Install Test 1/2: (No Input) succeeded' \ 223 | || error_and_proceed '## Use Auto-Install Test 1/2: (No Input) failed'; 224 | 225 | log 'info' '## Use Auto-Install Test 2/2: (Specific version)'; 226 | cleanup || log 'error' 'Cleanup failed?!'; 227 | 228 | ( 229 | tfenv use 1.0.1 || exit 1; 230 | check_default_version 1.0.1 || exit 1; 231 | ) && log info '## Use Auto-Install Test 2/2: (Specific version) succeeded' \ 232 | || error_and_proceed '## Use Auto-Install Test 2/2: (Specific version) failed'; 233 | 234 | 235 | log 'info' 'Install invalid specific version'; 236 | cleanup || log 'error' 'Cleanup failed?!'; 237 | 238 | neg_tests__desc=( 239 | 'specific version' 240 | 'latest:word' 241 | ); 242 | 243 | neg_tests__kv=( 244 | '9.9.9' 245 | 'latest:word' 246 | ); 247 | 248 | neg_tests_count=${#neg_tests__desc[@]}; 249 | 250 | for ((test_iter=0; test_iter<${neg_tests_count}; ++test_iter )) ; do 251 | cleanup || log 'error' 'Cleanup failed?!'; 252 | test_num=$((test_iter + 1)); 253 | desc=${neg_tests__desc[${test_iter}]} 254 | k="${neg_tests__kv[${test_iter}]}"; 255 | expected_error_message="No versions matching '${k}' found in remote"; 256 | log 'info' "## Invalid Version Test ${test_num}/${neg_tests_count}: ${desc} ( ${k} )"; 257 | [ -z "$(tfenv install "${k}" 2>&1 | grep "${expected_error_message}")" ] \ 258 | && error_and_proceed "Installing invalid version ${k}"; 259 | done; 260 | 261 | if [ "${#errors[@]}" -gt 0 ]; then 262 | log 'warn' '===== The following install_and_use tests failed ====='; 263 | for error in "${errors[@]}"; do 264 | log 'warn' "\t${error}"; 265 | done 266 | log 'error' 'Test failure(s): install_and_use'; 267 | else 268 | log 'info' 'All install_and_use tests passed'; 269 | fi; 270 | 271 | exit 0; 272 | -------------------------------------------------------------------------------- /test/test_list.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | ##################### 48 | # Begin Script Body # 49 | ##################### 50 | 51 | declare -a errors=(); 52 | 53 | log 'info' '### List local versions'; 54 | cleanup || log 'error' "Cleanup failed?!"; 55 | 56 | for v in 0.7.2 0.7.13 0.9.1 0.9.2 v0.9.11 0.14.6; do 57 | log 'info' "## Installing version ${v} to construct list"; 58 | tfenv install "${v}" \ 59 | && log 'debug' "Install of version ${v} succeeded" \ 60 | || error_and_proceed "Install of version ${v} failed"; 61 | done; 62 | 63 | log 'info' '## Ensuring tfenv list success with no default set'; 64 | tfenv list \ 65 | && log 'debug' "List succeeded with no default set" \ 66 | || error_and_proceed "List failed with no default set"; 67 | 68 | tfenv use 0.14.6; 69 | 70 | log 'info' '## Comparing "tfenv list" with default set'; 71 | result="$(tfenv list)"; 72 | expected="$(cat << EOS 73 | * 0.14.6 (set by $(tfenv version-file)) 74 | 0.9.11 75 | 0.9.2 76 | 0.9.1 77 | 0.7.13 78 | 0.7.2 79 | EOS 80 | )"; 81 | 82 | [ "${expected}" == "${result}" ] \ 83 | && log 'info' 'List matches expectation.' \ 84 | || error_and_proceed "List mismatch.\nExpected:\n${expected}\nGot:\n${result}"; 85 | 86 | if [ "${#errors[@]}" -gt 0 ]; then 87 | log 'warn' "===== The following list tests failed ====="; 88 | for error in "${errors[@]}"; do 89 | log 'warn' "\t${error}"; 90 | done; 91 | log 'error' 'List test failure(s)'; 92 | else 93 | log 'info' 'All list tests passed.'; 94 | fi; 95 | 96 | exit 0; 97 | -------------------------------------------------------------------------------- /test/test_symlink.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | ##################### 48 | # Begin Script Body # 49 | ##################### 50 | 51 | declare -a errors=(); 52 | 53 | log 'info' '### Testing symlink functionality'; 54 | 55 | TFENV_BIN_DIR='/tmp/tfenv-test'; 56 | log 'info' "## Creating/clearing ${TFENV_BIN_DIR}" 57 | rm -rf "${TFENV_BIN_DIR}" && mkdir "${TFENV_BIN_DIR}"; 58 | log 'info' "## Symlinking ${PWD}/bin/* into ${TFENV_BIN_DIR}"; 59 | ln -s "${PWD}"/bin/* "${TFENV_BIN_DIR}"; 60 | 61 | cleanup || log 'error' 'Cleanup failed?!'; 62 | 63 | log 'info' '## Installing 1.6.1'; 64 | ${TFENV_BIN_DIR}/tfenv install 1.6.1 || error_and_proceed 'Install failed'; 65 | 66 | log 'info' '## Using 1.6.1'; 67 | ${TFENV_BIN_DIR}/tfenv use 1.6.1 || error_and_proceed 'Use failed'; 68 | 69 | log 'info' '## Check-Version for 1.6.1'; 70 | check_active_version 1.6.1 || error_and_proceed 'Version check failed'; 71 | 72 | if [ "${#errors[@]}" -gt 0 ]; then 73 | log 'warn' '===== The following symlink tests failed ====='; 74 | for error in "${errors[@]}"; do 75 | log 'warn' "\t${error}"; 76 | done; 77 | log 'error' 'Symlink test failure(s)'; 78 | exit 1; 79 | else 80 | log 'info' 'All symlink tests passed.'; 81 | fi; 82 | 83 | exit 0; 84 | -------------------------------------------------------------------------------- /test/test_uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | ##################### 48 | # Begin Script Body # 49 | ##################### 50 | 51 | declare -a errors=(); 52 | 53 | function test_uninstall() { 54 | local k="${1}"; 55 | local v="${2}"; 56 | tfenv install "${v}" || return 1; 57 | tfenv uninstall "${v}" || return 1; 58 | log 'info' 'Confirming uninstall success; an error indicates success:'; 59 | check_active_version "${v}" && return 1 || return 0; 60 | }; 61 | 62 | log 'info' '### Test Suite: Uninstall Local Versions'; 63 | cleanup || log 'error' 'Cleanup failed?!'; 64 | 65 | tests__keywords=( 66 | '0.9.1' 67 | '0.11.15-oci' 68 | 'latest' 69 | 'latest:^0.8' 70 | 'v0.14.6' 71 | ); 72 | 73 | tests__versions=( 74 | '0.9.1' 75 | '0.11.15-oci' 76 | "$(tfenv list-remote | head -n1)" 77 | "$(tfenv list-remote | grep -e "^0.8" | head -n1)" 78 | '0.14.6' 79 | ); 80 | 81 | tests_count=${#tests__keywords[@]}; 82 | 83 | for ((test_num=0; test_num<${tests_count}; ++test_num )) ; do 84 | keyword=${tests__keywords[${test_num}]}; 85 | version=${tests__versions[${test_num}]}; 86 | log 'info' "Test $(( ${test_num} + 1 ))/${tests_count}: Testing uninstall of version ${version} via keyword ${keyword}"; 87 | test_uninstall "${keyword}" "${version}" \ 88 | && log info "Test uninstall of version ${version} (via ${keyword}) succeeded" \ 89 | || error_and_proceed "Test uninstall of version ${version} (via ${keyword}) failed"; 90 | done; 91 | 92 | echo "### Uninstall removes versions directory" 93 | cleanup || error_and_die "Cleanup failed?!" 94 | ( 95 | tfenv install 0.12.1 || exit 1 96 | tfenv install 0.12.2 || exit 1 97 | [ -d "./versions" ] || exit 1 98 | tfenv uninstall 0.12.1 || exit 1 99 | [ -d "./versions" ] || exit 1 100 | tfenv uninstall 0.12.2 || exit 1 101 | [ -d "./versions" ] && exit 1 || exit 0 102 | ) || error_and_proceed "Removing last version deletes versions directory" 103 | 104 | if [ "${#errors[@]}" -gt 0 ]; then 105 | log 'warn' "===== The following list tests failed ====="; 106 | for error in "${errors[@]}"; do 107 | log 'warn' "\t${error}"; 108 | done; 109 | log 'error' 'List test failure(s)'; 110 | else 111 | log 'info' 'All list tests passed.'; 112 | fi; 113 | exit 0; 114 | -------------------------------------------------------------------------------- /test/test_use_latestallowed.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | ##################### 48 | # Begin Script Body # 49 | ##################### 50 | 51 | declare -a errors=(); 52 | 53 | cleanup || log 'error' 'Cleanup failed?!'; 54 | 55 | 56 | log 'info' '### Install latest-allowed normal version (#.#.#)'; 57 | 58 | echo "terraform { 59 | required_version = \"~> 1.1.0\" 60 | }" > latest_allowed.tf; 61 | 62 | ( 63 | tfenv install latest-allowed; 64 | tfenv use latest-allowed; 65 | check_active_version 1.1.9; 66 | ) || error_and_proceed 'Latest allowed version does not match'; 67 | 68 | cleanup || log 'error' 'Cleanup failed?!'; 69 | 70 | 71 | log 'info' '### Install latest-allowed tagged version (#.#.#-tag#)' 72 | 73 | echo "terraform { 74 | required_version = \"<=0.13.0-rc1\" 75 | }" > latest_allowed.tf; 76 | 77 | ( 78 | tfenv install latest-allowed; 79 | tfenv use latest-allowed; 80 | check_active_version 0.13.0-rc1; 81 | ) || error_and_proceed 'Latest allowed tagged-version does not match'; 82 | 83 | cleanup || log 'error' 'Cleanup failed?!'; 84 | 85 | 86 | log 'info' '### Install latest-allowed incomplete version (#.#.)' 87 | 88 | echo "terraform { 89 | required_version = \"~> 0.12\" 90 | }" >> latest_allowed.tf; 91 | 92 | ( 93 | tfenv install latest-allowed; 94 | tfenv use latest-allowed; 95 | check_active_version 0.15.5; 96 | ) || error_and_proceed 'Latest allowed incomplete-version does not match'; 97 | 98 | cleanup || log 'error' 'Cleanup failed?!'; 99 | 100 | 101 | log 'info' '### Install latest-allowed with TFENV_AUTO_INSTALL'; 102 | 103 | echo "terraform { 104 | required_version = \"~> 1.0.0\" 105 | }" >> latest_allowed.tf; 106 | echo 'latest-allowed' > .terraform-version; 107 | 108 | ( 109 | TFENV_AUTO_INSTALL=true terraform version; 110 | check_active_version 1.0.11; 111 | ) || error_and_proceed 'Latest allowed auto-installed version does not match'; 112 | 113 | cleanup || log 'error' 'Cleanup failed?!'; 114 | 115 | 116 | log 'info' '### Install latest-allowed with TFENV_AUTO_INSTALL & -chdir'; 117 | 118 | mkdir -p chdir-dir 119 | echo "terraform { 120 | required_version = \"~> 0.14.3\" 121 | }" >> chdir-dir/latest_allowed.tf; 122 | echo 'latest-allowed' > chdir-dir/.terraform-version 123 | 124 | ( 125 | TFENV_AUTO_INSTALL=true terraform -chdir=chdir-dir version; 126 | check_active_version 0.14.11 chdir-dir; 127 | ) || error_and_proceed 'Latest allowed version from -chdir does not match'; 128 | 129 | cleanup || log 'error' 'Cleanup failed?!'; 130 | 131 | if [ "${#errors[@]}" -gt 0 ]; then 132 | log 'warn' '===== The following use_latestallowed tests failed ====='; 133 | for error in "${errors[@]}"; do 134 | log 'warn' "\t${error}"; 135 | done; 136 | log 'error' 'use_latestallowed test failure(s)'; 137 | else 138 | log 'info' 'All use_latestallowed tests passed.'; 139 | fi; 140 | 141 | exit 0; 142 | -------------------------------------------------------------------------------- /test/test_use_minrequired.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail; 4 | 5 | #################################### 6 | # Ensure we can execute standalone # 7 | #################################### 8 | 9 | function early_death() { 10 | echo "[FATAL] ${0}: ${1}" >&2; 11 | exit 1; 12 | }; 13 | 14 | if [ -z "${TFENV_ROOT:-""}" ]; then 15 | # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac 16 | readlink_f() { 17 | local target_file="${1}"; 18 | local file_name; 19 | 20 | while [ "${target_file}" != "" ]; do 21 | cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; 22 | file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; 23 | target_file="$(readlink "${file_name}")"; 24 | done; 25 | 26 | echo "$(pwd -P)/${file_name}"; 27 | }; 28 | 29 | TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; 30 | [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; 31 | else 32 | TFENV_ROOT="${TFENV_ROOT%/}"; 33 | fi; 34 | export TFENV_ROOT; 35 | 36 | if [ -n "${TFENV_HELPERS:-""}" ]; then 37 | log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; 38 | else 39 | [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; 40 | if source "${TFENV_ROOT}/lib/helpers.sh"; then 41 | log 'debug' 'Helpers sourced successfully'; 42 | else 43 | early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; 44 | fi; 45 | fi; 46 | 47 | ##################### 48 | # Begin Script Body # 49 | ##################### 50 | 51 | declare -a errors=(); 52 | 53 | cleanup || log 'error' 'Cleanup failed?!'; 54 | 55 | 56 | log 'info' '### Install min-required normal version (#.#.#)'; 57 | 58 | minv='1.6.0'; 59 | 60 | echo "terraform { 61 | required_version = \">=${minv}\" 62 | }" > min_required.tf; 63 | 64 | ( 65 | tfenv install min-required; 66 | tfenv use min-required; 67 | check_active_version "${minv}"; 68 | ) || error_and_proceed 'Min required version does not match'; 69 | 70 | cleanup || log 'error' 'Cleanup failed?!'; 71 | 72 | 73 | log 'info' '### Install min-required tagged version (#.#.#-tag#)' 74 | 75 | minv='1.5.0-rc1' 76 | 77 | echo "terraform { 78 | required_version = \">=${minv}\" 79 | }" > min_required.tf; 80 | 81 | ( 82 | tfenv install min-required; 83 | tfenv use min-required; 84 | check_active_version "${minv}"; 85 | ) || error_and_proceed 'Min required tagged-version does not match'; 86 | 87 | cleanup || log 'error' 'Cleanup failed?!'; 88 | 89 | 90 | log 'info' '### Install min-required incomplete version (#.#.)' 91 | 92 | minv='1.3'; 93 | 94 | echo "terraform { 95 | required_version = \">=${minv}\" 96 | }" >> min_required.tf; 97 | 98 | ( 99 | tfenv install min-required; 100 | tfenv use min-required; 101 | check_active_version "${minv}.0"; 102 | ) || error_and_proceed 'Min required incomplete-version does not match'; 103 | 104 | cleanup || log 'error' 'Cleanup failed?!'; 105 | 106 | 107 | log 'info' '### Install min-required with TFENV_AUTO_INSTALL'; 108 | 109 | minv='1.2.0'; 110 | 111 | echo "terraform { 112 | required_version = \">=${minv}\" 113 | }" >> min_required.tf; 114 | echo 'min-required' > .terraform-version; 115 | 116 | ( 117 | TFENV_AUTO_INSTALL=true terraform version; 118 | check_active_version "${minv}"; 119 | ) || error_and_proceed 'Min required auto-installed version does not match'; 120 | 121 | cleanup || log 'error' 'Cleanup failed?!'; 122 | 123 | 124 | log 'info' '### Install min-required with TFENV_AUTO_INSTALL & -chdir'; 125 | 126 | minv='1.1.0'; 127 | 128 | mkdir -p chdir-dir 129 | echo "terraform { 130 | required_version = \">=${minv}\" 131 | }" >> chdir-dir/min_required.tf; 132 | echo 'min-required' > chdir-dir/.terraform-version 133 | 134 | ( 135 | TFENV_AUTO_INSTALL=true terraform -chdir=chdir-dir version; 136 | check_active_version "${minv}" chdir-dir; 137 | ) || error_and_proceed 'Min required version from -chdir does not match'; 138 | 139 | cleanup || log 'error' 'Cleanup failed?!'; 140 | 141 | if [ "${#errors[@]}" -gt 0 ]; then 142 | log 'warn' '===== The following use_minrequired tests failed ====='; 143 | for error in "${errors[@]}"; do 144 | log 'warn' "\t${error}"; 145 | done; 146 | log 'error' 'use_minrequired test failure(s)'; 147 | else 148 | log 'info' 'All use_minrequired tests passed.'; 149 | fi; 150 | 151 | exit 0; 152 | --------------------------------------------------------------------------------