diff --git a/README.md b/README.md index b413ff6..9155dbe 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,17 @@ If a parameter is passed, available options: - `x.y.z` [Semver 2.0.0](https://semver.org/) string specifying the exact version to install - `latest` is a syntax to install latest version - `latest:` is a syntax to install latest version matching regex (used by grep -e) -- `min-required` is a syntax to recursively scan your Terraform files to detect which version is minimally required. See [required_version](https://www.terraform.io/docs/configuration/terraform.html) docs. Also [see min-required](#min-required) section below. +- `latest-allowed` is a syntax to scan your Terraform files to detect which version is maximally allowed. +- `min-required` is a syntax to scan your Terraform files to detect which version is minimally required. + +See [required_version](https://www.terraform.io/docs/configuration/terraform.html) docs. Also [see min-required & latest-allowed](#min-required) section below. ```console $ tfenv install $ tfenv install 0.7.0 $ tfenv install latest $ tfenv install latest:^0.8 +$ tfenv install latest-allowed $ tfenv install min-required ``` @@ -121,7 +125,8 @@ validation failure. If you use a [.terraform-version](#terraform-version-file) file, `tfenv install` (no argument) will install the version written in it. -#### min-required + +#### min-required & latest-allowed 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. @@ -133,9 +138,9 @@ terraform { ``` ```terraform -// this will detect 0.10.0 +// this will detect 0.10.8 (the latest 0.10.x release) terraform { - required_version = ">= 0.10.0, <0.12.3" + required_version = "~> 0.10.0, <0.12.3" } ``` diff --git a/lib/helpers.sh b/lib/helpers.sh index 69c9b5d..535232d 100755 --- a/lib/helpers.sh +++ b/lib/helpers.sh @@ -91,7 +91,7 @@ function check_active_version() { local active_version="$(${TFENV_ROOT}/bin/terraform ${maybe_chdir} version | grep '^Terraform')"; - if ! grep -E "^Terraform v${v}((-dev)|( \([a-f0-9]+\)))?\$" <(echo "${active_version}"); then + if ! grep -E "^Terraform v${v}((-dev)|( \([a-f0-9]+\)))?( is already installed)?\$" <(echo "${active_version}"); then log 'debug' "Expected version ${v} but found ${active_version}"; return 1; fi; @@ -124,6 +124,8 @@ function cleanup() { rm -rf ./versions; log 'debug' "Deleting ${pwd}/.terraform-version"; rm -rf ./.terraform-version; + log 'debug' "Deleting ${pwd}/latest_allowed.tf"; + rm -rf ./latest_allowed.tf; log 'debug' "Deleting ${pwd}/min_required.tf"; rm -rf ./min_required.tf; log 'debug' "Deleting ${pwd}/chdir-dir"; diff --git a/lib/tfenv-version-name.sh b/lib/tfenv-version-name.sh index 6607deb..3975769 100644 --- a/lib/tfenv-version-name.sh +++ b/lib/tfenv-version-name.sh @@ -40,6 +40,11 @@ function tfenv-version-name() { if [[ "${TFENV_VERSION}" =~ ^latest.*$ ]]; then log 'debug' "TFENV_VERSION uses 'latest' keyword: ${TFENV_VERSION}"; + if [[ "${TFENV_VERSION}" == latest-allowed ]]; then + TFENV_VERSION="$(tfenv-resolve-version)"; + log 'debug' "Resolved latest-allowed to: ${TFENV_VERSION}"; + fi; + if [[ "${TFENV_VERSION}" =~ ^latest\:.*$ ]]; then regex="${TFENV_VERSION##*\:}"; log 'debug' "'latest' keyword uses regex: ${regex}"; diff --git a/libexec/tfenv-resolve-version b/libexec/tfenv-resolve-version index ce5cfb0..d0b66bc 100755 --- a/libexec/tfenv-resolve-version +++ b/libexec/tfenv-resolve-version @@ -127,6 +127,30 @@ if [[ "${version_requested}" =~ ^min-required$ ]]; then version_requested="${min_required}"; fi; +if [[ "${version_requested}" =~ ^latest-allowed$ ]]; then + log 'debug' 'Detecting latest allowable version...'; + version_spec="$(grep -h required_version "${TFENV_DIR:-$(pwd)}"/{*.tf,*.tf.json} 2>/dev/null | rev | cut -d'"' -f2 | rev | cut -d, -f1)"; + version_num="$(echo "${version_spec}" | sed -E 's/[^0-9.]+//')"; + log 'debug' "Using ${version_num} from version spec: ${version_spec}"; + + case "${version_spec}" in + '>'*) + version_requested=latest; + ;; + '<='*) + version_requested="${version_num}"; + ;; + '~>'*) + version_without_rightmost="$(echo "${version_num}" | rev | cut -d. -f2- | rev)"; + version_requested="latest:^${version_without_rightmost}"; + ;; + *) + log 'error' "Unsupported version spec: '${version_spec}', only >, >=, <=, and ~> are supported."; + ;; + esac; + log 'debug' "Determined the requested version to be: ${version_requested}"; +fi; + if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then version="${version_requested%%\:*}"; regex="${version_requested##*\:}"; diff --git a/libexec/tfenv-uninstall b/libexec/tfenv-uninstall index eb240e8..83289c8 100755 --- a/libexec/tfenv-uninstall +++ b/libexec/tfenv-uninstall @@ -93,6 +93,10 @@ if [[ "${version_requested}" =~ ^min-required$ ]]; then log 'error' 'min-required is an unsupported option for uninstall'; fi; +if [[ "${version_requested}" == latest-allowed ]]; then + log 'error' 'latest-allowed is an unsupported option for uninstall'; +fi; + if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then version="${version_requested%%\:*}"; regex="${version_requested##*\:}"; diff --git a/test/test_use_latestallowed.sh b/test/test_use_latestallowed.sh new file mode 100755 index 0000000..10af0d5 --- /dev/null +++ b/test/test_use_latestallowed.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -uo pipefail; + +#################################### +# Ensure we can execute standalone # +#################################### + +function early_death() { + echo "[FATAL] ${0}: ${1}" >&2; + exit 1; +}; + +if [ -z "${TFENV_ROOT:-""}" ]; then + # http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac + readlink_f() { + local target_file="${1}"; + local file_name; + + while [ "${target_file}" != "" ]; do + cd "$(dirname ${target_file})" || early_death "Failed to 'cd \$(dirname ${target_file})' while trying to determine TFENV_ROOT"; + file_name="$(basename "${target_file}")" || early_death "Failed to 'basename \"${target_file}\"' while trying to determine TFENV_ROOT"; + target_file="$(readlink "${file_name}")"; + done; + + echo "$(pwd -P)/${file_name}"; + }; + + TFENV_ROOT="$(cd "$(dirname "$(readlink_f "${0}")")/.." && pwd)"; + [ -n ${TFENV_ROOT} ] || early_death "Failed to 'cd \"\$(dirname \"\$(readlink_f \"${0}\")\")/..\" && pwd' while trying to determine TFENV_ROOT"; +else + TFENV_ROOT="${TFENV_ROOT%/}"; +fi; +export TFENV_ROOT; + +if [ -n "${TFENV_HELPERS:-""}" ]; then + log 'debug' 'TFENV_HELPERS is set, not sourcing helpers again'; +else + [ "${TFENV_DEBUG:-0}" -gt 0 ] && echo "[DEBUG] Sourcing helpers from ${TFENV_ROOT}/lib/helpers.sh"; + if source "${TFENV_ROOT}/lib/helpers.sh"; then + log 'debug' 'Helpers sourced successfully'; + else + early_death "Failed to source helpers from ${TFENV_ROOT}/lib/helpers.sh"; + fi; +fi; + +##################### +# Begin Script Body # +##################### + +declare -a errors=(); + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed normal version (#.#.#)'; + +echo "terraform { + required_version = \"~> 1.1.0\" +}" > latest_allowed.tf; + +( + tfenv install latest-allowed; + tfenv use latest-allowed; + check_active_version 1.1.9; +) || error_and_proceed 'Latest allowed version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed tagged version (#.#.#-tag#)' + +echo "terraform { + required_version = \"<=0.13.0-rc1\" +}" > latest_allowed.tf; + +( + tfenv install latest-allowed; + tfenv use latest-allowed; + check_active_version 0.13.0-rc1; +) || error_and_proceed 'Latest allowed tagged-version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed incomplete version (#.#.)' + +echo "terraform { + required_version = \"~> 0.12\" +}" >> latest_allowed.tf; + +( + tfenv install latest-allowed; + tfenv use latest-allowed; + check_active_version 0.15.5; +) || error_and_proceed 'Latest allowed incomplete-version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed with TFENV_AUTO_INSTALL'; + +echo "terraform { + required_version = \"~> 1.0.0\" +}" >> latest_allowed.tf; +echo 'latest-allowed' > .terraform-version; + +( + TFENV_AUTO_INSTALL=true terraform version; + check_active_version 1.0.11; +) || error_and_proceed 'Latest allowed auto-installed version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed with TFENV_AUTO_INSTALL & -chdir'; + +mkdir -p chdir-dir +echo "terraform { + required_version = \"~> 0.14.3\" +}" >> chdir-dir/latest_allowed.tf; +echo 'latest-allowed' > chdir-dir/.terraform-version + +( + TFENV_AUTO_INSTALL=true terraform -chdir=chdir-dir version; + check_active_version 0.14.11 chdir-dir; +) || error_and_proceed 'Latest allowed version from -chdir does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + +if [ "${#errors[@]}" -gt 0 ]; then + log 'warn' '===== The following use_latestallowed tests failed ====='; + for error in "${errors[@]}"; do + log 'warn' "\t${error}"; + done; + log 'error' 'use_latestallowed test failure(s)'; +else + log 'info' 'All use_latestallowed tests passed.'; +fi; + +exit 0;