From 5b7436498ef7d008e41ca2eb913ff845d7944acf Mon Sep 17 00:00:00 2001 From: Vahid Haghighat Date: Sun, 1 Dec 2024 10:21:47 -0700 Subject: [PATCH 1/4] feat: Added latest-allowed-stable option. --- lib/helpers.sh | 21 +++++++ libexec/tfenv-resolve-version | 34 ++++++++++- ...wed.sh => test_use_latestallowedstable.sh} | 56 +++++++++++-------- 3 files changed, 86 insertions(+), 25 deletions(-) rename test/{test_use_latestallowed.sh => test_use_latestallowedstable.sh} (67%) diff --git a/lib/helpers.sh b/lib/helpers.sh index 535232d..822469f 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,25 @@ function check_dependencies() { alias grep=ggrep; fi; }; + +function generate_regex_for_latest_allowed_stable() { + local constraint="$1" + + # Extract major, minor, patch, and optional pre-release tag + local major minor patch pre_release + IFS='.-' read -r major minor patch pre_release <<< "$constraint" + + # Start building the regex pattern + local regex="^${major}\." + regex+="(" + regex+="${minor}\.${patch}-${pre_release}" + regex+="|$(seq 0 $(($minor - 1)) | sed 's/[0-9]*/&\.[0-9]+|/g' | tr -d '\n')" + regex="${regex%|}" + regex+=")$" + + echo "$regex" +} + 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_latestallowed.sh b/test/test_use_latestallowedstable.sh similarity index 67% rename from test/test_use_latestallowed.sh rename to test/test_use_latestallowedstable.sh index 10af0d5..13ad876 100755 --- a/test/test_use_latestallowed.sh +++ b/test/test_use_latestallowedstable.sh @@ -53,62 +53,74 @@ declare -a errors=(); cleanup || log 'error' 'Cleanup failed?!'; -log 'info' '### Install latest-allowed normal version (#.#.#)'; +log 'info' '### Install latest-allowed-stable normal version (#.#.#)'; echo "terraform { required_version = \"~> 1.1.0\" -}" > latest_allowed.tf; +}" > latest_allowed_stable.tf; ( - tfenv install latest-allowed; - tfenv use latest-allowed; + tfenv install latest-allowed-stable; + tfenv use latest-allowed-stable; check_active_version 1.1.9; -) || error_and_proceed 'Latest allowed version does not match'; +) || error_and_proceed 'Latest allowed stable version does not match'; cleanup || log 'error' 'Cleanup failed?!'; - -log 'info' '### Install latest-allowed tagged version (#.#.#-tag#)' +log 'info' '### Install latest-allowed-stable tagged version (#.#.#-tag#)' echo "terraform { required_version = \"<=0.13.0-rc1\" -}" > latest_allowed.tf; +}" > latest_allowed_stable.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'; + 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 incomplete version (#.#.)' +log 'info' '### Install latest-allowed-stable incomplete version (#.#.)' echo "terraform { required_version = \"~> 0.12\" -}" >> latest_allowed.tf; +}" >> latest_allowed_stable.tf; ( - tfenv install latest-allowed; - tfenv use latest-allowed; + tfenv install latest-allowed-stable; + tfenv use latest-allowed-stable; check_active_version 0.15.5; -) || error_and_proceed 'Latest allowed incomplete-version does not match'; +) || error_and_proceed 'Latest allowed stable incomplete-version does not match'; cleanup || log 'error' 'Cleanup failed?!'; -log 'info' '### Install latest-allowed with TFENV_AUTO_INSTALL'; +log 'info' '### Install latest-allowed-stable with TFENV_AUTO_INSTALL'; echo "terraform { required_version = \"~> 1.0.0\" -}" >> latest_allowed.tf; -echo 'latest-allowed' > .terraform-version; +}" >> 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 auto-installed version does not match'; +) || error_and_proceed 'Latest allowed stable auto-installed version does not match'; cleanup || log 'error' 'Cleanup failed?!'; @@ -119,7 +131,7 @@ mkdir -p chdir-dir echo "terraform { required_version = \"~> 0.14.3\" }" >> chdir-dir/latest_allowed.tf; -echo 'latest-allowed' > chdir-dir/.terraform-version +echo 'latest-allowed-stable' > chdir-dir/.terraform-version ( TFENV_AUTO_INSTALL=true terraform -chdir=chdir-dir version; From bcabba329dd755ea3a87d0b56451096f5043a4a7 Mon Sep 17 00:00:00 2001 From: Vahid Haghighat Date: Sun, 1 Dec 2024 10:25:33 -0700 Subject: [PATCH 2/4] doc: Added latest-allowed-stable description to README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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. From ec21220c3369dfcbbd51ea4a19f20bf5443fc4f5 Mon Sep 17 00:00:00 2001 From: Vahid Haghighat Date: Sun, 1 Dec 2024 10:38:00 -0700 Subject: [PATCH 3/4] fix: Removed unused function. --- lib/helpers.sh | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/helpers.sh b/lib/helpers.sh index 822469f..c031227 100755 --- a/lib/helpers.sh +++ b/lib/helpers.sh @@ -152,24 +152,6 @@ function check_dependencies() { fi; }; -function generate_regex_for_latest_allowed_stable() { - local constraint="$1" - - # Extract major, minor, patch, and optional pre-release tag - local major minor patch pre_release - IFS='.-' read -r major minor patch pre_release <<< "$constraint" - - # Start building the regex pattern - local regex="^${major}\." - regex+="(" - regex+="${minor}\.${patch}-${pre_release}" - regex+="|$(seq 0 $(($minor - 1)) | sed 's/[0-9]*/&\.[0-9]+|/g' | tr -d '\n')" - regex="${regex%|}" - regex+=")$" - - echo "$regex" -} - export -f check_dependencies; source "$TFENV_ROOT/lib/tfenv-exec.sh"; From 76648159e6a615d5e927b60f7eade6a1ce66ae84 Mon Sep 17 00:00:00 2001 From: Vahid Haghighat Date: Sun, 1 Dec 2024 10:40:05 -0700 Subject: [PATCH 4/4] fix: Added back test for latestallowed. --- test/test_use_latestallowed.sh | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100755 test/test_use_latestallowed.sh 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;