diff --git a/CHANGELOG.md b/CHANGELOG.md index a371754..81d12d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ ## 2.0.0 (Unreleased) - * New logging library - * New bash4 dependency + * New logging and debugging library * Massive testing, logging and loading refactoring * Fix to 'use' logic: don't overwrite .terraform-version files + * Fix #167 - Never invoke use automatically on install - multiple code and testing changes for new logic + * Fix to not use 0.12.22 during testing which reports its version incorrectly + * Introduce tfenv-resolve-version to deduplicate translation of requested version into actual version + * README.md updates ## 1.0.2 (October 29, 2019) diff --git a/README.md b/README.md index ac54cbe..7cf4e46 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,11 @@ include ::tfenv ### tfenv install [version] -Install a specific version of Terraform. Available options for version: +Install a specific version of Terraform. + +If no parameter is passed, the version to use is resolved automatically via .terraform-version files, defaulting to 'latest' if none are found. + +If a parameter is passed, available options: - `i.j.k` exact version to install - `latest` is a syntax to install latest version @@ -74,11 +78,11 @@ Install a specific version of Terraform. Available options for version: - `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. ```console +$ tfenv install $ tfenv install 0.7.0 $ tfenv install latest $ tfenv install latest:^0.8 $ tfenv install min-required -$ tfenv install ``` If `shasum` is present in the path, tfenv will verify the download against Hashicorp's published sha256 hash. @@ -146,6 +150,15 @@ Set the mechanism used for displaying download progress when downloading terrafo ##### `TFENV_DEBUG` +Integer (Default: 0) + +Set the debug level for TFENV. + +* 0: No debug output +* 1: Simple debug output +* 2: Extended debug output, with source file names and interactive debug shells on error +* 3: Debug level 2 + Bash execution tracing + ##### `TFENV_REMOTE` String (Default: https://releases.hashicorp.com) @@ -279,10 +292,12 @@ Defaults to the PID of the calling process. -### tfenv use <version> +### tfenv use [version] Switch a version to use +If no parameter is passed, the version to use is resolved automatically via .terraform-version files, defaulting to 'latest' if none are found. + `latest` is a syntax to use the latest installed version `latest:` is a syntax to use latest installed version matching regex (used by grep -e) @@ -290,6 +305,7 @@ Switch a version to use `min-required` will switch to the version minimally required by your terraform sources (see above `tfenv install`) ```console +$ tfenv use $ tfenv use min-required $ tfenv use 0.7.0 $ tfenv use latest diff --git a/lib/helpers.sh b/lib/helpers.sh index cfddfba..e97047f 100755 --- a/lib/helpers.sh +++ b/lib/helpers.sh @@ -34,6 +34,66 @@ fi; source "${TFENV_ROOT}/lib/bashlog.sh"; +resolve_version () { + declare version_requested version regex min_required version_file; + + declare arg="${1:-""}"; + + if [ -z "${arg}" ]; then + version_file="$(tfenv-version-file)"; + log 'debug' "Version File: ${version_file}"; + + if [ "${version_file}" != "${TFENV_ROOT}/version" ]; then + log 'debug' "Version File (${version_file}) is not the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version)"; + version_requested="$(cat "${version_file}")" \ + || log 'error' "Failed to open ${version_file}"; + + elif [ -f "${version_file}" ]; then + log 'debug' "Version File is the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version)"; + version_requested="$(cat "${version_file}")" \ + || log 'error' "Failed to open ${version_file}"; + + # Absolute fallback + if [ -z "${version_requested}" ]; then + log 'debug' 'Version file had no content. Falling back to "latest"'; + version_requested='latest'; + fi; + + else + log 'debug' "Version File is the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version) but it doesn't exist"; + log 'info' 'No version requested on the command line or in the version file search path. Installing "latest"'; + version_requested='latest'; + fi; + else + version_requested="${arg}"; + fi; + + log 'debug' "Version Requested: ${version_requested}"; + + if [[ "${version_requested}" =~ ^min-required$ ]]; then + log 'info' 'Detecting minimum required version...'; + min_required="$(tfenv-min-required)" \ + || log 'error' 'tfenv-min-required failed'; + + log 'info' "Minimum required version detected: ${min_required}"; + version_requested="${min_required}"; + fi; + + if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then + version="${version_requested%%\:*}"; + regex="${version_requested##*\:}"; + log 'debug' "Version uses latest keyword with regex: ${regex}"; + elif [[ "${version_requested}" =~ ^latest$ ]]; then + version="${version_requested}"; + regex="^[0-9]\+\.[0-9]\+\.[0-9]\+$"; + log 'debug' "Version uses latest keyword alone. Forcing regex to match stable versions only: ${regex}"; + else + version="${version_requested}"; + regex="^${version_requested}$"; + log 'debug' "Version is explicit: ${version}. Regex enforces the version: ${regex}"; + fi; +} + # Curl wrapper to switch TLS option for each OS function curlw () { local TLS_OPT="--tlsv1.2"; diff --git a/libexec/tfenv-install b/libexec/tfenv-install index 50efff9..33d39b8 100755 --- a/libexec/tfenv-install +++ b/libexec/tfenv-install @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -uo pipefail; #################################### @@ -61,60 +60,20 @@ done; [ "${#}" -gt 1 ] && log 'error' 'usage: tfenv install []'; -declare version_requested version regex; -declare arg="${1:-""}"; - -if [ -z "${arg}" ]; then - version_file="$(tfenv-version-file)"; - log 'debug' "Version File: ${version_file}"; - if [ "${version_file}" != "${TFENV_ROOT}/version" ]; then - log 'debug' "Version File (${version_file}) is not the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version)"; - version_requested="$(cat "${version_file}")" \ - || log 'error' "Failed to open ${version_file}"; - elif [ -f "${version_file}" ]; then - log 'debug' "Version File is the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version)"; - version_requested="$(cat "${version_file}")" \ - || log 'error' "Failed to open ${version_file}"; - else - log 'debug' "Version File is the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version) but it doesn't exist"; - log 'info' 'No version requested on the command line or in the version file search path. Installing "latest"'; - version_requested='latest'; - fi; -else - version_requested="${arg}"; -fi; - -log 'debug' "Version Requested: ${version_requested}"; +declare requested="${1:-""}"; -if [[ "${version_requested}" =~ ^min-required$ ]]; then - log 'info' 'Detecting minimal required version...'; - found_min_required="$(tfenv-min-required)"; +log debug "Resolving version with: tfenv-resolve-version ${requested}"; +declare resolved="$(tfenv-resolve-version ${requested})"; - if [[ $? -eq 0 ]]; then - log 'info' "Min required version is detected as ${found_min_required}"; - version_requested="${found_min_required}"; - else - exit 1; - fi; -fi; - -if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then - version="${version_requested%%\:*}"; - regex="${version_requested##*\:}"; -elif [[ "${version_requested}" =~ ^latest$ ]]; then - version="${version_requested}"; - regex="^[0-9]\+\.[0-9]\+\.[0-9]\+$"; -else - version="${version_requested}"; - regex="^${version_requested}$"; -fi; +declare version="${resolved%%\:*}"; +declare regex="${resolved##*\:}"; [ -n "${version}" ] || log 'error' 'Version is not specified. This should not be possible as we default to latest'; log 'debug' "Processing install for version ${version}, using regex ${regex}"; version="$(tfenv-list-remote | grep -e "${regex}" | head -n 1)"; -[ -n "${version}" ] || log 'error' "No versions matching '${arg}' found in remote"; +[ -n "${version}" ] || log 'error' "No versions matching '${requested}' found in remote"; dst_path="${TFENV_ROOT}/versions/${version}"; if [ -f "${dst_path}/terraform" ]; then @@ -279,5 +238,4 @@ while IFS= read -r unzip_line; do log 'info' "${unzip_line}"; done < <(printf '%s\n' "${unzip_output}"); -log 'info' "Installation of terraform v${version} successful"; -tfenv-use "${version}"; +log 'info' "Installation of terraform v${version} successful. To make this your default version, run 'tfenv use ${version}'"; diff --git a/libexec/tfenv-resolve-version b/libexec/tfenv-resolve-version new file mode 100755 index 0000000..d488e52 --- /dev/null +++ b/libexec/tfenv-resolve-version @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# Usage: tfenv resolve-version [] +# Summary: Resolve the version to action based on the environment and optional input token + +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; + +# Ensure libexec and bin are in $PATH +for dir in libexec bin; do + case ":${PATH}:" in + *:${TFENV_ROOT}/${dir}:*) log 'debug' "\$PATH already contains '${TFENV_ROOT}/${dir}', not adding it again";; + *) + log 'debug' "\$PATH does not contain '${TFENV_ROOT}/${dir}', prepending and exporting it now"; + export PATH="${TFENV_ROOT}/${dir}:${PATH}"; + ;; + esac; +done; + +##################### +# Begin Script Body # +##################### + +declare version_requested version regex min_required version_file; + +declare arg="${1:-""}"; + +if [ -z "${arg}" ]; then + version_file="$(tfenv-version-file)"; + log 'debug' "Version File: ${version_file}"; + + if [ "${version_file}" != "${TFENV_ROOT}/version" ]; then + log 'debug' "Version File (${version_file}) is not the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version)"; + version_requested="$(cat "${version_file}")" \ + || log 'error' "Failed to open ${version_file}"; + + elif [ -f "${version_file}" ]; then + log 'debug' "Version File is the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version)"; + version_requested="$(cat "${version_file}")" \ + || log 'error' "Failed to open ${version_file}"; + + # Absolute fallback + if [ -z "${version_requested}" ]; then + log 'debug' 'Version file had no content. Falling back to "latest"'; + version_requested='latest'; + fi; + + else + log 'debug' "Version File is the default \${TFENV_ROOT}/version (${TFENV_ROOT}/version) but it doesn't exist"; + log 'info' 'No version requested on the command line or in the version file search path. Installing "latest"'; + version_requested='latest'; + fi; +else + version_requested="${arg}"; +fi; + +log 'debug' "Version Requested: ${version_requested}"; + +if [[ "${version_requested}" =~ ^min-required$ ]]; then + log 'info' 'Detecting minimum required version...'; + min_required="$(tfenv-min-required)" \ + || log 'error' 'tfenv-min-required failed'; + + log 'info' "Minimum required version detected: ${min_required}"; + version_requested="${min_required}"; +fi; + +if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then + version="${version_requested%%\:*}"; + regex="${version_requested##*\:}"; + log 'debug' "Version uses latest keyword with regex: ${regex}"; +elif [[ "${version_requested}" =~ ^latest$ ]]; then + version="${version_requested}"; + regex="^[0-9]\+\.[0-9]\+\.[0-9]\+$"; + log 'debug' "Version uses latest keyword alone. Forcing regex to match stable versions only: ${regex}"; +else + version="${version_requested}"; + regex="^${version_requested}$"; + log 'debug' "Version is explicit: ${version}. Regex enforces the version: ${regex}"; +fi; + +echo "${version}:${regex}"; diff --git a/libexec/tfenv-use b/libexec/tfenv-use index d701f28..2a96a2d 100755 --- a/libexec/tfenv-use +++ b/libexec/tfenv-use @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -uo pipefail; #################################### @@ -59,40 +58,21 @@ done; # Begin Script Body # ##################### -[ "${#}" -ne 1 ] && log 'error' 'usage: tfenv use '; - -declare version_requested version regex min_required version_file; - -version_requested="${1}"; +[ "${#}" -gt 1 ] && log 'error' 'usage: tfenv use []'; -if [[ "${version_requested}" =~ ^min-required$ ]]; then - log 'info' 'Detecting minimum required version...'; - min_required="$(tfenv-min-required)" \ - || log 'error' 'tfenv-min-required failed'; +[ -d "${TFENV_ROOT}/versions" ] \ + || log 'error' 'No versions of terraform installed. Please install one with: tfenv install'; - log 'info' "Minimum required version detected: ${min_required}"; - version_requested="${min_required}"; -fi; +declare requested="${1:-""}"; -if [[ "${version_requested}" =~ ^latest\:.*$ ]]; then - version="${version_requested%%\:*}"; - regex="${version_requested##*\:}"; - log 'debug' "Version uses latest keyword with regex: ${regex}"; -elif [[ "${version_requested}" =~ ^latest$ ]]; then - version="${version_requested}"; - regex="^[0-9]\+\.[0-9]\+\.[0-9]\+$"; - log 'debug' "Version uses latest keyword alone. Forcing regex to match stable versions only: ${regex}"; -else - version="${version_requested}"; - regex="^${version_requested}$"; - log 'debug' "Version is explicit: ${version}. Regex enforces the version: ${regex}"; -fi; +log debug "Resolving version with: tfenv-resolve-version ${requested}"; +declare resolved="$(tfenv-resolve-version ${requested})"; -[ -d "${TFENV_ROOT}/versions" ] \ - || log 'error' 'No versions of terraform installed. Please install one with: tfenv install'; +declare version="${resolved%%\:*}"; +declare regex="${resolved##*\:}"; log 'debug' "Searching ${TFENV_ROOT}/versions for latest version matching ${regex}"; -version="$(\ls "${TFENV_ROOT}/versions" \ +declare version="$(\ls "${TFENV_ROOT}/versions" \ | sort -t'.' -k 1nr,1 -k 2nr,2 -k 3nr,3 \ | grep -e "${regex}" \ | head -n 1 diff --git a/libexec/tfenv-version-file b/libexec/tfenv-version-file index 7dfb976..e4f0ce7 100755 --- a/libexec/tfenv-version-file +++ b/libexec/tfenv-version-file @@ -80,7 +80,7 @@ find_local_version_file() { } if ! find_local_version_file "${TFENV_DIR:-${PWD}}"; then - if ! find_local_version_file "${HOME}"; then + if ! find_local_version_file "${HOME:-/}"; then log 'debug' "No version file found in search paths. Defaulting to TFENV_ROOT: ${TFENV_ROOT}/version"; echo "${TFENV_ROOT}/version"; fi; diff --git a/libexec/tfenv-version-name b/libexec/tfenv-version-name index 64bb7c0..dbd71fd 100755 --- a/libexec/tfenv-version-name +++ b/libexec/tfenv-version-name @@ -103,5 +103,5 @@ fi; if [ -d "${TFENV_ROOT}/versions/${TFENV_VERSION}" ]; then echo "${TFENV_VERSION}"; else - log 'error' "version '${TFENV_VERSION}' is not installed (set by ${TFENV_VERSION_FILE})"; + log 'warn' "version '${TFENV_VERSION}' is not installed (set by ${TFENV_VERSION_FILE})"; fi; diff --git a/test/run.sh b/test/run.sh index 6abb640..3a24dd5 100755 --- a/test/run.sh +++ b/test/run.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -uo pipefail; #################################### diff --git a/test/test_install_and_use.sh b/test/test_install_and_use.sh index 8bea949..65d608d 100755 --- a/test/test_install_and_use.sh +++ b/test/test_install_and_use.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -uo pipefail; #################################### @@ -54,6 +53,7 @@ test_install_and_use() { local v="${1}"; tfenv install "${k}" || return 1; check_installed_version "${v}" || return 1; + tfenv use "${k}" || return 1; check_active_version "${v}" || return 1; return 0; }; @@ -64,6 +64,7 @@ test_install_and_use_overridden() { local v="${1}"; tfenv install "${k}" || return 1; check_installed_version "${v}" || return 1; + tfenv use "${k}" || return 1; check_default_version "${v}" || return 1; return 0; } @@ -116,8 +117,11 @@ done; cleanup || log 'error' 'Cleanup failed?!'; log 'info' '## ${HOME}/.terraform-version Test Preparation'; -declare v1="$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | head -n 2 | tail -n 1)"; -declare v2="$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | head -n 1)"; + +# 0.12.22 reports itself as 0.12.21 and breaks testing +declare v1="$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | grep -v '0.12.22' | head -n 2 | tail -n 1)"; +declare v2="$(tfenv list-remote | grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+$" | grep -v '0.12.22' | head -n 1)"; + if [ -f "${HOME}/.terraform-version" ]; then log 'info' "Backing up ${HOME}/.terraform-version to ${HOME}/.terraform-version.bup"; mv "${HOME}/.terraform-version" "${HOME}/.terraform-version.bup"; diff --git a/test/test_list.sh b/test/test_list.sh index 365555f..c640e27 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -60,6 +60,8 @@ for v in 0.7.2 0.7.13 0.9.1 0.9.2 0.9.11; do || error_and_proceed "Install of version ${v} failed"; done; +tfenv use 0.9.11 + log 'info' '## Comparing "tfenv list" to expectations'; result="$(tfenv list)"; expected="$(cat << EOS