diff --git a/README.md b/README.md index 1887e0a..1f8ef95 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ If a parameter is passed, available options: - `latest` is a syntax to install latest version - `latest:` is a syntax to install latest version matching regex (used by grep -e) - `latest-allowed` is a syntax to scan your Terraform files to detect which version is maximally allowed. +- `latest-allowed-stable` is the same as `latest-allowed`, but only considers stable versions and not pre-releases. - `min-required` is a syntax to scan your Terraform files to detect which version is minimally required. See [required_version](https://developer.hashicorp.com/terraform/language/settings) docs. Also [see min-required & latest-allowed](#min-required) section below. @@ -104,6 +105,7 @@ $ tfenv install 0.7.0 $ tfenv install latest $ tfenv install latest:^0.8 $ tfenv install latest-allowed +$ tfenv install latest-allowed-stable $ tfenv install min-required ``` @@ -131,7 +133,7 @@ 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 & latest-allowed +#### min-required & latest-allowed & latest-allowed-stable 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. diff --git a/lib/helpers.sh b/lib/helpers.sh index 535232d..c031227 100755 --- a/lib/helpers.sh +++ b/lib/helpers.sh @@ -126,6 +126,8 @@ function cleanup() { rm -rf ./.terraform-version; log 'debug' "Deleting ${pwd}/latest_allowed.tf"; rm -rf ./latest_allowed.tf; + log 'debug' "Deleting ${pwd}/latest_allowed_stable.tf"; + rm -rf ./latest_allowed_stable.tf; log 'debug' "Deleting ${pwd}/min_required.tf"; rm -rf ./min_required.tf; log 'debug' "Deleting ${pwd}/chdir-dir"; @@ -149,6 +151,7 @@ function check_dependencies() { alias grep=ggrep; fi; }; + export -f check_dependencies; source "$TFENV_ROOT/lib/tfenv-exec.sh"; diff --git a/libexec/tfenv-resolve-version b/libexec/tfenv-resolve-version index 69468e5..4d0e325 100755 --- a/libexec/tfenv-resolve-version +++ b/libexec/tfenv-resolve-version @@ -127,7 +127,7 @@ if [[ "${version_requested}" =~ ^min-required$ ]]; then version_requested="${min_required}"; fi; -if [[ "${version_requested}" =~ ^latest-allowed$ ]]; then +if [[ "${version_requested}" =~ ^latest-allowed(-stable)?$ ]]; then log 'debug' 'Detecting latest allowable version...'; version_spec="$(grep -h required_version "${TFENV_DIR:-$(pwd)}"/{*.tf,*.tf.json} 2>/dev/null | { IFS='"' read -r _ ver _; echo "${ver%%,*}"; })"; version_num="$(echo "${version_spec}" | sed -E 's/[^0-9.]+//')"; @@ -138,11 +138,39 @@ if [[ "${version_requested}" =~ ^latest-allowed$ ]]; then version_requested=latest; ;; '<='*) - version_requested="${version_num}"; + IFS='.-' read -r major minor patch pre_release <<< "${version_num}" + if [[ "${version_requested}" =~ latest-allowed-stable$ && -n "${pre_release}" ]]; then + constraint_regex="^" + if [[ "$major" != 0 ]]; then + constraint_regex="^$(seq 0 $((major - 1)) | sed "s/[0-9]*/&\\\.[0-9]\\\+\\\.[0-9]\\\+\\\|/g" | tr -d '\n')" + fi + + if [[ "$minor" != 0 ]]; then + constraint_regex+="$(seq 0 $((minor - 1)) | sed "s/[0-9]*/${major}\\\.&\\\.[0-9]\\\+\\\|/g" | tr -d '\n')" + fi + + constraint_regex="${constraint_regex%\\|}" # Remove trailing '|' + constraint_regex+="$" + version_requested="latest:${constraint_regex}" + else + version_requested="${version_num}"; + fi ;; '~>'*) version_without_rightmost="${version_num%.*}"; - version_requested="latest:^${version_without_rightmost}"; + if [[ "${version_without_rightmost}" == "${version_num}" ]]; then + version_requested=latest + elif [[ "${version_requested}" =~ ^latest-allowed-stable$ ]]; then + IFS='.' read -ra version_without_rightmost_parts <<< "${version_without_rightmost}" + version_without_rightmost_count=${#version_without_rightmost_parts[@]} + version_requested="latest:^${version_without_rightmost}" + for (( i = version_without_rightmost_count; i < 3; i++ )); do + version_requested+="\.[0-9]\+" + done + version_requested+="$" + else + version_requested="latest:^${version_without_rightmost}" + fi ;; *) log 'error' "Unsupported version spec: '${version_spec}', only >, >=, <=, and ~> are supported."; diff --git a/test/test_use_latestallowedstable.sh b/test/test_use_latestallowedstable.sh new file mode 100755 index 0000000..13ad876 --- /dev/null +++ b/test/test_use_latestallowedstable.sh @@ -0,0 +1,153 @@ +#!/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-stable normal version (#.#.#)'; + +echo "terraform { + required_version = \"~> 1.1.0\" +}" > latest_allowed_stable.tf; + +( + tfenv install latest-allowed-stable; + tfenv use latest-allowed-stable; + check_active_version 1.1.9; +) || error_and_proceed 'Latest allowed stable version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + +log 'info' '### Install latest-allowed-stable tagged version (#.#.#-tag#)' + +echo "terraform { + required_version = \"<=0.13.0-rc1\" +}" > latest_allowed_stable.tf; + +( + tfenv install latest-allowed-stable; + tfenv use latest-allowed-stable; + check_active_version 0.12.31; +) || error_and_proceed 'Latest allowed stable tagged-version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +echo "terraform { + required_version = \"<=1.1.0-alpha20211006\" +}" > latest_allowed_stable.tf; + +( + tfenv install latest-allowed-stable; + tfenv use latest-allowed-stable; + check_active_version 1.0.11; +) || error_and_proceed 'Latest allowed stable tagged-version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed-stable incomplete version (#.#.)' + +echo "terraform { + required_version = \"~> 0.12\" +}" >> latest_allowed_stable.tf; + +( + tfenv install latest-allowed-stable; + tfenv use latest-allowed-stable; + check_active_version 0.15.5; +) || error_and_proceed 'Latest allowed stable incomplete-version does not match'; + +cleanup || log 'error' 'Cleanup failed?!'; + + +log 'info' '### Install latest-allowed-stable with TFENV_AUTO_INSTALL'; + +echo "terraform { + required_version = \"~> 1.0.0\" +}" >> latest_allowed_stable.tf; +echo 'latest-allowed-stable' > .terraform-version; + +( + TFENV_AUTO_INSTALL=true terraform version; + check_active_version 1.0.11; +) || error_and_proceed 'Latest allowed stable 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-stable' > 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;