From b0038d854c14423aa4a2d848ba8e051a0640773a Mon Sep 17 00:00:00 2001 From: Kaniska Date: Mon, 21 Sep 2026 15:40:21 +0000 Subject: [PATCH 1/4] Fallback for version resolution over unauthenticated git for feature installation. --- .github/workflows/version-resolution.yaml | 41 +++++ scripts/sync-version-resolution.sh | 40 +++++ scripts/version-resolution.sh | 167 ++++++++++++++++++ src/copilot-cli/devcontainer-feature.json | 2 +- src/copilot-cli/install.sh | 16 +- src/copilot-cli/scripts/version-resolution.sh | 167 ++++++++++++++++++ .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 2 + .../scripts/version-resolution.sh | 167 ++++++++++++++++++ .../devcontainer-feature.json | 2 +- src/docker-outside-of-docker/install.sh | 2 + .../scripts/version-resolution.sh | 167 ++++++++++++++++++ src/git-lfs/devcontainer-feature.json | 2 +- src/git-lfs/install.sh | 2 + src/git-lfs/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/github-cli/devcontainer-feature.json | 2 +- src/github-cli/install.sh | 2 + src/github-cli/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/go/devcontainer-feature.json | 2 +- src/go/install.sh | 12 +- src/go/scripts/version-resolution.sh | 167 ++++++++++++++++++ .../devcontainer-feature.json | 2 +- src/kubectl-helm-minikube/install.sh | 2 + .../scripts/version-resolution.sh | 167 ++++++++++++++++++ src/nix/devcontainer-feature.json | 2 +- src/nix/install.sh | 1 + src/nix/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/node/devcontainer-feature.json | 2 +- src/node/install.sh | 2 + src/node/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/php/devcontainer-feature.json | 2 +- src/php/install.sh | 2 + src/php/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/powershell/devcontainer-feature.json | 2 +- src/powershell/install.sh | 4 +- src/powershell/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/python/devcontainer-feature.json | 2 +- src/python/install.sh | 2 + src/python/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/rust/devcontainer-feature.json | 2 +- src/rust/install.sh | 2 + src/rust/scripts/version-resolution.sh | 167 ++++++++++++++++++ src/terraform/devcontainer-feature.json | 2 +- src/terraform/install.sh | 2 + src/terraform/scripts/version-resolution.sh | 167 ++++++++++++++++++ test/_global/version-resolution/cases.sh | 33 ++++ test/_global/version-resolution/test.sh | 136 ++++++++++++++ 47 files changed, 2808 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/version-resolution.yaml create mode 100644 scripts/sync-version-resolution.sh create mode 100644 scripts/version-resolution.sh create mode 100644 src/copilot-cli/scripts/version-resolution.sh create mode 100644 src/docker-in-docker/scripts/version-resolution.sh create mode 100644 src/docker-outside-of-docker/scripts/version-resolution.sh create mode 100644 src/git-lfs/scripts/version-resolution.sh create mode 100644 src/github-cli/scripts/version-resolution.sh create mode 100644 src/go/scripts/version-resolution.sh create mode 100644 src/kubectl-helm-minikube/scripts/version-resolution.sh create mode 100644 src/nix/scripts/version-resolution.sh create mode 100644 src/node/scripts/version-resolution.sh create mode 100644 src/php/scripts/version-resolution.sh create mode 100644 src/powershell/scripts/version-resolution.sh create mode 100644 src/python/scripts/version-resolution.sh create mode 100644 src/rust/scripts/version-resolution.sh create mode 100644 src/terraform/scripts/version-resolution.sh create mode 100644 test/_global/version-resolution/cases.sh create mode 100644 test/_global/version-resolution/test.sh diff --git a/.github/workflows/version-resolution.yaml b/.github/workflows/version-resolution.yaml new file mode 100644 index 000000000..c9820e00f --- /dev/null +++ b/.github/workflows/version-resolution.yaml @@ -0,0 +1,41 @@ +name: "CI - Version Resolution Fallbacks" + +on: + push: + branches: + - main + paths: + - ".github/workflows/version-resolution.yaml" + - "scripts/version-resolution.sh" + - "scripts/sync-version-resolution.sh" + - "src/**/install.sh" + - "src/**/scripts/version-resolution.sh" + - "test/_global/version-resolution/**" + pull_request: + paths: + - ".github/workflows/version-resolution.yaml" + - "scripts/version-resolution.sh" + - "scripts/sync-version-resolution.sh" + - "src/**/install.sh" + - "src/**/scripts/version-resolution.sh" + - "test/_global/version-resolution/**" + +jobs: + version-resolution: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Check synchronized resolver copies + run: bash scripts/sync-version-resolution.sh --check + + - name: Test fallback resolution + run: bash test/_global/version-resolution/test.sh + + - name: Lint resolver scripts + run: | + shellcheck -e SC2072 \ + scripts/version-resolution.sh \ + scripts/sync-version-resolution.sh \ + test/_global/version-resolution/test.sh \ + test/_global/version-resolution/cases.sh \ No newline at end of file diff --git a/scripts/sync-version-resolution.sh b/scripts/sync-version-resolution.sh new file mode 100644 index 000000000..0851fc371 --- /dev/null +++ b/scripts/sync-version-resolution.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SOURCE_FILE="${ROOT_DIR}/scripts/version-resolution.sh" +FEATURES=( + copilot-cli + docker-in-docker + docker-outside-of-docker + git-lfs + github-cli + go + kubectl-helm-minikube + nix + node + php + powershell + python + rust + terraform +) + +check_only=false +if [ "${1:-}" = "--check" ]; then + check_only=true +fi + +for feature in "${FEATURES[@]}"; do + target_dir="${ROOT_DIR}/src/${feature}/scripts" + target_file="${target_dir}/version-resolution.sh" + if ${check_only}; then + if ! cmp -s "${SOURCE_FILE}" "${target_file}"; then + echo "${target_file} is not synchronized with ${SOURCE_FILE}." >&2 + exit 1 + fi + else + mkdir -p "${target_dir}" + cp "${SOURCE_FILE}" "${target_file}" + fi +done \ No newline at end of file diff --git a/scripts/version-resolution.sh b/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/copilot-cli/devcontainer-feature.json b/src/copilot-cli/devcontainer-feature.json index f4db33ced..ee16a60e8 100644 --- a/src/copilot-cli/devcontainer-feature.json +++ b/src/copilot-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "copilot-cli", - "version": "1.1.3", + "version": "1.1.4", "name": "GitHub Copilot CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/copilot-cli", "description": "Installs the GitHub Copilot CLI.", diff --git a/src/copilot-cli/install.sh b/src/copilot-cli/install.sh index ad4a19701..6c34e8c87 100755 --- a/src/copilot-cli/install.sh +++ b/src/copilot-cli/install.sh @@ -8,6 +8,7 @@ # Maintainer: The VS Code and Codespaces Teams CLI_VERSION=${VERSION:-"latest"} +REQUESTED_CLI_VERSION="${CLI_VERSION}" set -e @@ -31,13 +32,7 @@ check_packages() { fi } -resolve_prerelease_version() { - local repo_versions="${1:?resolve_prerelease_version requires the copilot-cli repo tags as input}" - printf '%s\n' "${repo_versions}" \ - | awk '{print $2}' | sed 's|refs/tags/||' \ - | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$' \ - | sort -V | tail -n1 -} +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" download_from_github() { local release_url=$1 @@ -71,9 +66,8 @@ install_using_github() { if [ "${CLI_VERSION}" = "latest" ]; then download_from_github "https://github.com/github/copilot-cli/releases/latest/download/${cli_filename}" elif [ "${CLI_VERSION}" = "prerelease" ]; then - - prerelease_version="$(resolve_prerelease_version "$(git ls-remote --tags https://github.com/github/copilot-cli)")" - download_from_github "https://github.com/github/copilot-cli/releases/download/${prerelease_version}/${cli_filename}" + find_version_from_git_tags CLI_VERSION "https://github.com/github/copilot-cli" "tags/v" "." "false" "(-[0-9]+)" + download_from_github "https://github.com/github/copilot-cli/releases/download/v${CLI_VERSION}/${cli_filename}" else # Install specific version @@ -91,7 +85,7 @@ echo "Downloading GitHub Copilot CLI..." install_using_github # Create a flag file if using "latest" or "prerelease" so the postStartCommand knows to auto-update -if [ "${CLI_VERSION}" = "latest" ] || [ "${CLI_VERSION}" = "prerelease" ]; then +if [ "${REQUESTED_CLI_VERSION}" = "latest" ] || [ "${REQUESTED_CLI_VERSION}" = "prerelease" ]; then mkdir -p /etc/devcontainer-copilot-cli touch /etc/devcontainer-copilot-cli/auto-update fi diff --git a/src/copilot-cli/scripts/version-resolution.sh b/src/copilot-cli/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/copilot-cli/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 2325a19cd..c76df9661 100644 --- a/src/docker-in-docker/devcontainer-feature.json +++ b/src/docker-in-docker/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "docker-in-docker", - "version": "4.1.1", + "version": "4.1.2", "name": "Docker (Docker-in-Docker)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-in-docker", "description": "Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.", diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index cd25b2fb8..fe2018a59 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -209,6 +209,8 @@ get_github_api_repo_url() { echo "${url/https:\/\/github.com/https:\/\/api.github.com\/repos}/releases" } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + ########################################### # Start docker-in-docker installation ########################################### diff --git a/src/docker-in-docker/scripts/version-resolution.sh b/src/docker-in-docker/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/docker-in-docker/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index 9ab361cd6..7ebbd4783 100644 --- a/src/docker-outside-of-docker/devcontainer-feature.json +++ b/src/docker-outside-of-docker/devcontainer-feature.json @@ -1,7 +1,7 @@ { "id": "docker-outside-of-docker", - "version": "1.10.1", + "version": "1.10.2", "name": "Docker (docker-outside-of-docker)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker", "description": "Re-use the host docker socket, adding the Docker CLI to a container. Feature invokes a script to enable using a forwarded Docker socket within a container to run Docker commands.", diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index 2a1515565..62ab49e23 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -179,6 +179,8 @@ get_github_api_repo_url() { echo "${url/https:\/\/github.com/https:\/\/api.github.com\/repos}/releases" } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + install_compose_switch_fallback() { compose_switch_url=$1 repo_url=$(get_github_api_repo_url "${compose_switch_url}") diff --git a/src/docker-outside-of-docker/scripts/version-resolution.sh b/src/docker-outside-of-docker/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/docker-outside-of-docker/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/git-lfs/devcontainer-feature.json b/src/git-lfs/devcontainer-feature.json index fe23e02cf..a8d65b714 100644 --- a/src/git-lfs/devcontainer-feature.json +++ b/src/git-lfs/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "git-lfs", - "version": "1.2.5", + "version": "1.2.6", "name": "Git Large File Support (LFS)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/git-lfs", "description": "Installs Git Large File Support (Git LFS) along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like git and curl.", diff --git a/src/git-lfs/install.sh b/src/git-lfs/install.sh index 71066c7b3..8b5bd16e9 100755 --- a/src/git-lfs/install.sh +++ b/src/git-lfs/install.sh @@ -219,6 +219,8 @@ install_using_github() { rm -rf /tmp/git-lfs /tmp/tmp-gnupg } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + export DEBIAN_FRONTEND=noninteractive # Install git, curl, gpg, dirmngr and debian-archive-keyring if missing diff --git a/src/git-lfs/scripts/version-resolution.sh b/src/git-lfs/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/git-lfs/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/github-cli/devcontainer-feature.json b/src/github-cli/devcontainer-feature.json index 9b7f51e89..2f63ef76c 100644 --- a/src/github-cli/devcontainer-feature.json +++ b/src/github-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "github-cli", - "version": "1.1.2", + "version": "1.1.3", "name": "GitHub CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/github-cli", "description": "Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.", diff --git a/src/github-cli/install.sh b/src/github-cli/install.sh index 162803620..2d6dc9148 100755 --- a/src/github-cli/install.sh +++ b/src/github-cli/install.sh @@ -211,6 +211,8 @@ install_deb_using_github() { rm -rf /tmp/ghcli } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + export DEBIAN_FRONTEND=noninteractive # Install curl, apt-transport-https, curl, gpg, or dirmngr, git if missing diff --git a/src/github-cli/scripts/version-resolution.sh b/src/github-cli/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/github-cli/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/go/devcontainer-feature.json b/src/go/devcontainer-feature.json index b61e93605..9a06623c7 100644 --- a/src/go/devcontainer-feature.json +++ b/src/go/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "go", - "version": "1.3.4", + "version": "1.3.5", "name": "Go", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/go", "description": "Installs Go and common Go utilities. Auto-detects latest version and installs needed dependencies.", diff --git a/src/go/install.sh b/src/go/install.sh index db0ac7977..545d7ab9a 100755 --- a/src/go/install.sh +++ b/src/go/install.sh @@ -191,6 +191,14 @@ elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then USERNAME=root fi +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + +get_go_release_versions() { + curl -fsSL "https://go.dev/dl/?mode=json&include=all" \ + | grep -oE '"version"[[:space:]]*:[[:space:]]*"go[0-9]+\.[0-9]+(\.[0-9]+)?"' \ + | sed -E 's/^.*"go([0-9.]+)"$/\1/' +} + export DEBIAN_FRONTEND=noninteractive check_packages ca-certificates gnupg2 tar gcc make pkg-config @@ -220,7 +228,7 @@ if ! [ -f /usr/bin/find ]; then fi # Get closest match for version number specified -find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" +find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" "" "the official Go release index" get_go_release_versions "1.27.1" architecture="$(uname -m)" case $architecture in @@ -263,7 +271,7 @@ if [[ "${TARGET_GO_VERSION}" != "none" ]] && [[ "$(go version 2>/dev/null)" != * ((minor=minor-1)) TARGET_GO_VERSION="${major}.${minor}" # Look for latest version from previous minor release - find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" + find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" "" "the official Go release index" get_go_release_versions "1.27.1" else ((breakfix=breakfix-1)) if [ "${breakfix}" = "0" ]; then diff --git a/src/go/scripts/version-resolution.sh b/src/go/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/go/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/kubectl-helm-minikube/devcontainer-feature.json b/src/kubectl-helm-minikube/devcontainer-feature.json index a88aebde6..87ce23815 100644 --- a/src/kubectl-helm-minikube/devcontainer-feature.json +++ b/src/kubectl-helm-minikube/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "kubectl-helm-minikube", - "version": "1.3.1", + "version": "1.3.2", "name": "Kubectl, Helm, and Minikube", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/kubectl-helm-minikube", "description": "Installs latest version of kubectl, Helm, and optionally minikube. Auto-detects latest versions and installs needed dependencies.", diff --git a/src/kubectl-helm-minikube/install.sh b/src/kubectl-helm-minikube/install.sh index 40901d3cb..031955c1e 100755 --- a/src/kubectl-helm-minikube/install.sh +++ b/src/kubectl-helm-minikube/install.sh @@ -146,6 +146,8 @@ check_packages() { } # Ensure apt is in non-interactive to avoid prompts +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + export DEBIAN_FRONTEND=noninteractive # Install dependencies diff --git a/src/kubectl-helm-minikube/scripts/version-resolution.sh b/src/kubectl-helm-minikube/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/kubectl-helm-minikube/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/nix/devcontainer-feature.json b/src/nix/devcontainer-feature.json index f2e956d8a..92e167d8f 100644 --- a/src/nix/devcontainer-feature.json +++ b/src/nix/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "nix", - "version": "1.4.0", + "version": "1.4.1", "name": "Nix Package Manager", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/nix", "description": "Installs the Nix package manager and optionally a set of packages.", diff --git a/src/nix/install.sh b/src/nix/install.sh index ca19f658a..f66943489 100755 --- a/src/nix/install.sh +++ b/src/nix/install.sh @@ -20,6 +20,7 @@ fi # Import common utils . ./utils.sh +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" detect_user USERNAME diff --git a/src/nix/scripts/version-resolution.sh b/src/nix/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/nix/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 5e0970b2d..5e0b8063e 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "2.1.0", + "version": "2.1.1", "name": "Node.js (via nvm), yarn and pnpm.", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/node", "description": "Installs Node.js, nvm, yarn, pnpm, and needed dependencies.", diff --git a/src/node/install.sh b/src/node/install.sh index 0e277812e..e10a4a044 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -289,6 +289,8 @@ if ! type git > /dev/null 2>&1; then check_packages git fi +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + # Adjust node version if required if [ "${NODE_VERSION}" = "none" ]; then export NODE_VERSION= diff --git a/src/node/scripts/version-resolution.sh b/src/node/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/node/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/php/devcontainer-feature.json b/src/php/devcontainer-feature.json index 6abdc965b..0a43843bc 100644 --- a/src/php/devcontainer-feature.json +++ b/src/php/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "php", - "version": "1.1.5", + "version": "1.1.6", "name": "PHP", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/php", "options": { diff --git a/src/php/install.sh b/src/php/install.sh index 531b16518..baa4fdedb 100755 --- a/src/php/install.sh +++ b/src/php/install.sh @@ -161,6 +161,8 @@ find_prev_version_from_git_tags() { set -e } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + # Install PHP Composer addcomposer() { "${PHP_SRC}" -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" diff --git a/src/php/scripts/version-resolution.sh b/src/php/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/php/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/powershell/devcontainer-feature.json b/src/powershell/devcontainer-feature.json index 906d4bbcf..7e0089283 100644 --- a/src/powershell/devcontainer-feature.json +++ b/src/powershell/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "powershell", - "version": "2.0.3", + "version": "2.0.4", "name": "PowerShell", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/powershell", "description": "Installs PowerShell along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.", diff --git a/src/powershell/install.sh b/src/powershell/install.sh index 9856cc5e0..2764cf3f5 100755 --- a/src/powershell/install.sh +++ b/src/powershell/install.sh @@ -353,6 +353,8 @@ get_github_api_repo_url() { echo "${url/https:\/\/github.com/https:\/\/api.github.com\/repos}/releases/latest" } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + install_prev_pwsh() { pwsh_url=$1 @@ -393,7 +395,7 @@ install_using_github() { # Check if we need to find a preview version or stable version if [[ "${POWERSHELL_VERSION}" == *"preview"* ]] || [ "${POWERSHELL_VERSION}" = "preview" ] || [[ "${POWERSHELL_VERSION}" == *"-rc."* ]]; then echo "Finding preview version..." - find_preview_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" + find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" "tags/v" "." "false" "(-preview\.[0-9]+|-rc\.[0-9]+)" "GitHub REST API" _github_rest_version_candidates "7.7.0-preview.4" else find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" fi diff --git a/src/powershell/scripts/version-resolution.sh b/src/powershell/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/powershell/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/python/devcontainer-feature.json b/src/python/devcontainer-feature.json index 6a4121415..8012bbc8e 100644 --- a/src/python/devcontainer-feature.json +++ b/src/python/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "python", - "version": "1.8.0", + "version": "1.8.1", "name": "Python", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/python", "description": "Installs the provided version of Python, as well as PIPX, and other common Python utilities. JupyterLab is conditionally installed with the python feature. Note: May require source code compilation.", diff --git a/src/python/install.sh b/src/python/install.sh index be895927d..aa4571bbc 100755 --- a/src/python/install.sh +++ b/src/python/install.sh @@ -442,6 +442,8 @@ check_packages() { esac } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + add_symlink() { if [[ ! -d "${CURRENT_PATH}" ]]; then ln -s -r "${INSTALL_PATH}" "${CURRENT_PATH}" diff --git a/src/python/scripts/version-resolution.sh b/src/python/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/python/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/rust/devcontainer-feature.json b/src/rust/devcontainer-feature.json index f94d0dc18..b8baf3afc 100644 --- a/src/rust/devcontainer-feature.json +++ b/src/rust/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "rust", - "version": "1.6.0", + "version": "1.6.1", "name": "Rust", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/rust", "description": "Installs Rust, common Rust utilities, and their required dependencies", diff --git a/src/rust/install.sh b/src/rust/install.sh index 163dfc5c0..ee295ff12 100755 --- a/src/rust/install.sh +++ b/src/rust/install.sh @@ -351,6 +351,8 @@ case ${download_architecture} in ;; esac +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + # Install Rust umask 0002 if ! grep -e "^rustlang:" /etc/group > /dev/null 2>&1; then diff --git a/src/rust/scripts/version-resolution.sh b/src/rust/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/rust/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 634d865ef..5355239cd 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.5.0", + "version": "1.5.1", "name": "Terraform, tflint, and TFGrunt", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/terraform", "description": "Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects latest version and installs needed dependencies.", diff --git a/src/terraform/install.sh b/src/terraform/install.sh index 43779f825..0b29a5fbd 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -349,6 +349,8 @@ install_cosign() { echo "Installation of cosign succeeded with ${COSIGN_VERSION}." } +. "$(dirname "${BASH_SOURCE[0]}")/scripts/version-resolution.sh" + # Install 'cosign' for validating signatures # https://docs.sigstore.dev/cosign/overview/ ensure_cosign() { diff --git a/src/terraform/scripts/version-resolution.sh b/src/terraform/scripts/version-resolution.sh new file mode 100644 index 000000000..80ce6841f --- /dev/null +++ b/src/terraform/scripts/version-resolution.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# Resolve version requests from git tags, with a caller-provided fallback resolver. +# The fallback function must print candidate versions, one per line. +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + local version_suffix_regex=${6:-""} + local fallback_name=${7:-""} + local fallback_function=${8:-""} + local known_good_version=${9:-""} + + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi + if [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + + if [ "${requested_version}" = "none" ]; then + return + fi + + local escaped_separator=${separator//./\.} + local patch_regex="${escaped_separator}[0-9]+" + if [ "${last_part_optional}" = "true" ]; then + patch_regex="(${patch_regex})?" + fi + local version_regex="[0-9]+${escaped_separator}[0-9]+${patch_regex}${version_suffix_regex//./\.}" + + # Fully qualified versions are user assertions. Do not require the network + # to prove that a requested release exists. + if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then + echo "${variable_name}=${requested_version}" + return + fi + + local version_list="" + local git_output="" + if git_output="$(git ls-remote --tags "${repository}" 2>/dev/null)"; then + version_list="$(_extract_version_candidates "${git_output}" "${prefix}" "${separator}" "${version_regex}")" + fi + + if [ -z "${version_list}" ]; then + echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -n "${fallback_function}" ]; then + echo "(*) Trying ${fallback_name:-alternate version source}." >&2 + local fallback_output="" + if fallback_output="$(${fallback_function} "${repository}" "${prefix}" 2>/dev/null)"; then + version_list="$(_normalize_version_candidates "${fallback_output}" "${separator}" "${version_regex}")" + fi + if [ -n "${version_list}" ]; then + echo "(*) Resolved version using ${fallback_name:-alternate version source}." >&2 + fi + fi + fi + + local resolved_version="" + resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="${known_good_version}" + echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 + fi + + if [ -z "${resolved_version}" ]; then + echo "(!) Unable to resolve '${requested_version}' for ${variable_name}. Tried git tags at ${repository}${fallback_name:+ and ${fallback_name}}. Specify an exact version to avoid remote resolution." >&2 + return 1 + fi + + declare -g "${variable_name}=${resolved_version}" + echo "${variable_name}=${resolved_version}" +} + +_github_rest_version_candidates() { + local repository=$1 + local prefix=${2:-"tags/v"} + local slug=${repository#https://github.com/} + slug=${slug%/} + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + + curl -fsSL "https://api.github.com/repos/${slug}/tags?per_page=100" \ + | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | sed -E 's/^.*"([^"]+)"$/\1/' \ + | sed "s|^${tag_prefix}||" +} + +_known_good_version_for_repository() { + case ${1%/} in + https://github.com/docker/compose) echo "5.5.1" ;; + https://github.com/docker/compose-switch) echo "1.0.5" ;; + https://github.com/docker/buildx) echo "0.37.1" ;; + https://github.com/cli/cli) echo "2.101.0" ;; + https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; + https://github.com/helm/helm) echo "4.3.0" ;; + https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; + https://github.com/kubernetes/minikube) echo "1.39.0" ;; + https://github.com/NixOS/nix) echo "2.35.2" ;; + https://github.com/nvm-sh/nvm) echo "0.40.8" ;; + https://github.com/php/php-src) echo "8.5.10" ;; + https://github.com/xdebug/xdebug) echo "3.5.3" ;; + https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; + https://github.com/python/cpython) echo "3.14.7" ;; + https://github.com/openssl/openssl) echo "4.0.2" ;; + https://github.com/sigstore/cosign) echo "3.1.3" ;; + https://github.com/rust-lang/rust) echo "1.98.1" ;; + https://github.com/hashicorp/terraform) echo "1.16.3" ;; + https://github.com/terraform-linters/tflint) echo "0.64.0" ;; + https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; + https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; + https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; + https://github.com/github/copilot-cli) echo "1.0.87-0" ;; + esac +} + +_extract_version_candidates() { + local input=$1 + local prefix=$2 + local separator=$3 + local version_regex=$4 + local escaped_prefix=${prefix//./\.} + + printf '%s\n' "${input}" \ + | grep -oE "${escaped_prefix}${version_regex}$" \ + | sed "s|^${prefix}||" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_normalize_version_candidates() { + local input=$1 + local separator=$2 + local version_regex=$3 + + printf '%s\n' "${input}" \ + | grep -oE "^${version_regex}$" \ + | tr "${separator}" "." \ + | sort -rVu || true +} + +_select_requested_version() { + local requested_version=$1 + local version_list=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + printf '%s\n' "${version_list}" | head -n 1 + return + fi + + printf '%s\n' "${version_list}" | grep -E -m 1 "^${requested_version//./\.}([.]|[-]|$)" || true +} + +_version_matches_request() { + local requested_version=$1 + local candidate=$2 + + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "stable" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "prerelease" ] || [ "${requested_version}" = "preview" ]; then + return 0 + fi + + echo "${candidate}" | grep -Eq "^${requested_version//./\.}([.]|[-]|$)" +} \ No newline at end of file diff --git a/test/_global/version-resolution/cases.sh b/test/_global/version-resolution/cases.sh new file mode 100644 index 000000000..db3cc39ce --- /dev/null +++ b/test/_global/version-resolution/cases.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 +# feature|label|repository|prefix|optional patch|suffix regex|request|fallback tag|expected +VERSION_RESOLUTION_CASES=( + "docker-in-docker|compose|https://github.com/docker/compose|tags/v|false||latest|v5.5.1|5.5.1" + "docker-in-docker|compose switch|https://github.com/docker/compose-switch|tags/v|false||latest|v1.0.5|1.0.5" + "docker-in-docker|buildx|https://github.com/docker/buildx|refs/tags/v|false||latest|v0.37.1|0.37.1" + "docker-outside-of-docker|compose|https://github.com/docker/compose|tags/v|false||latest|v5.5.1|5.5.1" + "docker-outside-of-docker|compose switch|https://github.com/docker/compose-switch|tags/v|false||latest|v1.0.5|1.0.5" + "github-cli|gh|https://github.com/cli/cli|tags/v|false||latest|v2.101.0|2.101.0" + "git-lfs|git lfs|https://github.com/git-lfs/git-lfs|tags/v|false||latest|v3.8.0|3.8.0" + "go|go|https://go.googlesource.com/go|tags/go|true||latest|1.27.1|1.27.1" + "kubectl-helm-minikube|kubectl|https://github.com/kubernetes/kubernetes|tags/v|false||latest|v1.37.0|1.37.0" + "kubectl-helm-minikube|helm|https://github.com/helm/helm|tags/v|false||latest|v4.3.0|4.3.0" + "kubectl-helm-minikube|minikube|https://github.com/kubernetes/minikube|tags/v|false||latest|v1.39.0|1.39.0" + "nix|nix|https://github.com/NixOS/nix|tags/|false||latest|2.35.2|2.35.2" + "node|nvm|https://github.com/nvm-sh/nvm|tags/v|false||latest|v0.40.8|0.40.8" + "php|php|https://github.com/php/php-src|tags/php-|false||latest|php-8.5.10|8.5.10" + "php|xdebug|https://github.com/xdebug/xdebug|tags/|false||latest|3.5.3|3.5.3" + "powershell|stable|https://github.com/PowerShell/PowerShell|tags/v|false||latest|v7.6.6|7.6.6" + "powershell|preview|https://github.com/PowerShell/PowerShell|tags/v|false|-preview\\.[0-9]+|preview|v7.7.0-preview.4|7.7.0-preview.4" + "powershell|release candidate|https://github.com/PowerShell/PowerShell|tags/v|false|-rc\\.[0-9]+|prerelease|v7.7.0-rc.1|7.7.0-rc.1" + "python|cpython|https://github.com/python/cpython|tags/v|false||latest|v3.14.7|3.14.7" + "python|openssl|https://github.com/openssl/openssl|openssl-|false||latest|openssl-4.0.2|4.0.2" + "python|cosign|https://github.com/sigstore/cosign|tags/v|false||latest|v3.1.3|3.1.3" + "rust|rust|https://github.com/rust-lang/rust|tags/|false||latest|1.98.1|1.98.1" + "copilot-cli|prerelease|https://github.com/github/copilot-cli|tags/v|false|(-[0-9]+)|prerelease|v1.0.87-0|1.0.87-0" + "terraform|terraform|https://github.com/hashicorp/terraform|tags/v|false||latest|v1.16.3|1.16.3" + "terraform|tflint|https://github.com/terraform-linters/tflint|tags/v|false||latest|v0.64.0|0.64.0" + "terraform|terragrunt|https://github.com/gruntwork-io/terragrunt|tags/v|false||latest|v1.1.5|1.1.5" + "terraform|tfsec|https://github.com/aquasecurity/tfsec|tags/v|false||latest|v1.28.14|1.28.14" + "terraform|terraform-docs|https://github.com/terraform-docs/terraform-docs|tags/v|false||latest|v0.24.0|0.24.0" +) \ No newline at end of file diff --git a/test/_global/version-resolution/test.sh b/test/_global/version-resolution/test.sh new file mode 100644 index 000000000..31dff2201 --- /dev/null +++ b/test/_global/version-resolution/test.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +source "${ROOT_DIR}/scripts/version-resolution.sh" +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/cases.sh" + +bash "${ROOT_DIR}/scripts/sync-version-resolution.sh" --check + +network_calls=0 +git() { + network_calls=$((network_calls + 1)) + return 1 +} + +# shellcheck disable=SC2329 +fallback_versions() { + printf '%s\n' "1.4.2" "1.3.9" +} + +curl() { + printf '[{"name":"%s"}]\n' "${FAKE_FALLBACK_TAG:-v2.3.4}" +} + +VERSION="1.2.3" +find_version_from_git_tags VERSION https://github.com/example/tool +if [ "${VERSION}" != "1.2.3" ] || [ "${network_calls}" -ne 0 ]; then + echo "Exact versions must not perform remote resolution." >&2 + exit 1 +fi + +git() { + printf '%s\n' \ + 'abc refs/tags/v3.1.0' \ + 'def refs/tags/v3.2.1' +} +VERSION="3.2" +find_version_from_git_tags VERSION https://github.com/example/tool >/tmp/version-resolution-output.log 2>&1 +if [ "${VERSION}" != "3.2.1" ] || grep -q "Trying" /tmp/version-resolution-output.log; then + echo "Primary git tag resolution did not select the requested version line." >&2 + exit 1 +fi + +git() { + network_calls=$((network_calls + 1)) + return 1 +} + +VERSION="latest" +find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'example releases API' fallback_versions 1.2.3 >/tmp/version-resolution-output.log 2>&1 +if [ "${VERSION}" != "1.4.2" ]; then + echo "Fallback resolver did not select the latest version." >&2 + exit 1 +fi +if ! grep -q "Trying example releases API" /tmp/version-resolution-output.log; then + echo "Fallback warning did not identify the alternate source." >&2 + exit 1 +fi + +fallback_versions() { + return 1 +} +VERSION="1.2" +find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'example releases API' fallback_versions 1.2.3 >/tmp/version-resolution-output.log 2>&1 +if [ "${VERSION}" != "1.2.3" ] || ! grep -q "known-good version 1.2.3" /tmp/version-resolution-output.log; then + echo "Compatible known-good fallback was not selected or reported." >&2 + exit 1 +fi + +VERSION="2.0" +if find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'example releases API' fallback_versions 1.2.3 >/tmp/version-resolution-error.log 2>&1; then + echo "An incompatible known-good version must be rejected." >&2 + exit 1 +fi +if ! grep -q "Specify an exact version" /tmp/version-resolution-error.log; then + echo "Final resolution error is not actionable." >&2 + exit 1 +fi + +fallback_versions() { + printf '%s\n' "not-a-version" "release-next" +} +VERSION="latest" +if find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'malformed source' fallback_versions '' >/tmp/version-resolution-error.log 2>&1; then + echo "Malformed fallback versions must be rejected." >&2 + exit 1 +fi + +VERSION="latest" +find_version_from_git_tags VERSION https://github.com/example/unpinned >/tmp/version-resolution-output.log 2>&1 +if [ "${VERSION}" != "2.3.4" ] || ! grep -q "GitHub REST API" /tmp/version-resolution-output.log; then + echo "Automatic GitHub REST fallback did not select and report a version." >&2 + exit 1 +fi + +for test_case in "${VERSION_RESOLUTION_CASES[@]}"; do + IFS='|' read -r feature label repository prefix last_part_optional suffix_regex request fallback_tag expected <<< "${test_case}" + # shellcheck disable=SC1090 + source "${ROOT_DIR}/src/${feature}/scripts/version-resolution.sh" + VERSION="${expected}" + rm -f /tmp/version-resolution-network-call + git() { + touch /tmp/version-resolution-network-call + return 1 + } + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" >/tmp/version-resolution-output.log 2>&1 + if [ -e /tmp/version-resolution-network-call ]; then + echo "${feature} ${label}: exact version performed remote resolution." >&2 + exit 1 + fi + + git() { + return 1 + } + FAKE_FALLBACK_TAG="${fallback_tag}" + VERSION="${request}" + if [ "${feature}" = "go" ]; then + go_fallback_versions() { + printf '%s\n' "${FAKE_FALLBACK_TAG}" + } + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "the official Go release index" go_fallback_versions "${expected}" >/tmp/version-resolution-output.log 2>&1 + else + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" >/tmp/version-resolution-output.log 2>&1 + fi + if [ "${VERSION}" != "${expected}" ]; then + echo "${feature} ${label}: expected ${expected}, got ${VERSION}." >&2 + exit 1 + fi + if ! grep -qE 'Trying (GitHub REST API|the official Go release index)' /tmp/version-resolution-output.log; then + echo "${feature} ${label}: fallback warning did not identify its source." >&2 + exit 1 + fi +done + +echo "Version resolution tests passed." \ No newline at end of file From 15a6715b3599562e5afbddbe679e9a048339ec5d Mon Sep 17 00:00:00 2001 From: Kaniska Date: Mon, 21 Sep 2026 18:31:12 +0000 Subject: [PATCH 2/4] Fixing test issues and version resolution logic --- scripts/version-resolution.sh | 28 +++++++++++-------- src/copilot-cli/scripts/version-resolution.sh | 28 +++++++++++-------- .../scripts/version-resolution.sh | 28 +++++++++++-------- .../scripts/version-resolution.sh | 28 +++++++++++-------- src/git-lfs/scripts/version-resolution.sh | 28 +++++++++++-------- src/github-cli/scripts/version-resolution.sh | 28 +++++++++++-------- src/go/scripts/version-resolution.sh | 28 +++++++++++-------- .../scripts/version-resolution.sh | 28 +++++++++++-------- src/nix/scripts/version-resolution.sh | 28 +++++++++++-------- src/node/scripts/version-resolution.sh | 28 +++++++++++-------- src/php/scripts/version-resolution.sh | 28 +++++++++++-------- src/powershell/scripts/version-resolution.sh | 28 +++++++++++-------- src/python/scripts/version-resolution.sh | 28 +++++++++++-------- src/rust/scripts/version-resolution.sh | 28 +++++++++++-------- src/terraform/scripts/version-resolution.sh | 28 +++++++++++-------- test/_global/version-resolution/test.sh | 14 +++++++++- 16 files changed, 253 insertions(+), 181 deletions(-) diff --git a/scripts/version-resolution.sh b/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/scripts/version-resolution.sh +++ b/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/copilot-cli/scripts/version-resolution.sh b/src/copilot-cli/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/copilot-cli/scripts/version-resolution.sh +++ b/src/copilot-cli/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/docker-in-docker/scripts/version-resolution.sh b/src/docker-in-docker/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/docker-in-docker/scripts/version-resolution.sh +++ b/src/docker-in-docker/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/docker-outside-of-docker/scripts/version-resolution.sh b/src/docker-outside-of-docker/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/docker-outside-of-docker/scripts/version-resolution.sh +++ b/src/docker-outside-of-docker/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/git-lfs/scripts/version-resolution.sh b/src/git-lfs/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/git-lfs/scripts/version-resolution.sh +++ b/src/git-lfs/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/github-cli/scripts/version-resolution.sh b/src/github-cli/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/github-cli/scripts/version-resolution.sh +++ b/src/github-cli/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/go/scripts/version-resolution.sh b/src/go/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/go/scripts/version-resolution.sh +++ b/src/go/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/kubectl-helm-minikube/scripts/version-resolution.sh b/src/kubectl-helm-minikube/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/kubectl-helm-minikube/scripts/version-resolution.sh +++ b/src/kubectl-helm-minikube/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/nix/scripts/version-resolution.sh b/src/nix/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/nix/scripts/version-resolution.sh +++ b/src/nix/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/node/scripts/version-resolution.sh b/src/node/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/node/scripts/version-resolution.sh +++ b/src/node/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/php/scripts/version-resolution.sh b/src/php/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/php/scripts/version-resolution.sh +++ b/src/php/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/powershell/scripts/version-resolution.sh b/src/powershell/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/powershell/scripts/version-resolution.sh +++ b/src/powershell/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/python/scripts/version-resolution.sh b/src/python/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/python/scripts/version-resolution.sh +++ b/src/python/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/rust/scripts/version-resolution.sh b/src/rust/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/rust/scripts/version-resolution.sh +++ b/src/rust/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/src/terraform/scripts/version-resolution.sh b/src/terraform/scripts/version-resolution.sh index 80ce6841f..2b2ff3045 100644 --- a/src/terraform/scripts/version-resolution.sh +++ b/src/terraform/scripts/version-resolution.sh @@ -14,18 +14,14 @@ find_version_from_git_tags() { local fallback_function=${8:-""} local known_good_version=${9:-""} - if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then - fallback_name="GitHub REST API" - fallback_function="_github_rest_version_candidates" - fi - if [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi - if [ "${requested_version}" = "none" ]; then return fi + local tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + local normalized_request=${requested_version#"${tag_prefix}"} + local escaped_separator=${separator//./\.} local patch_regex="${escaped_separator}[0-9]+" if [ "${last_part_optional}" = "true" ]; then @@ -35,8 +31,9 @@ find_version_from_git_tags() { # Fully qualified versions are user assertions. Do not require the network # to prove that a requested release exists. - if echo "${requested_version}" | grep -Eq "^${version_regex}$"; then - echo "${variable_name}=${requested_version}" + if echo "${normalized_request}" | grep -Eq "^${version_regex}$"; then + declare -g "${variable_name}=${normalized_request}" + echo "${variable_name}=${normalized_request}" return fi @@ -48,6 +45,10 @@ find_version_from_git_tags() { if [ -z "${version_list}" ]; then echo "(!) Unable to resolve '${requested_version}' from git tags at ${repository}." >&2 + if [ -z "${fallback_function}" ] && echo "${repository}" | grep -qE '^https://github.com/[^/]+/[^/]+/?$'; then + fallback_name="GitHub REST API" + fallback_function="_github_rest_version_candidates" + fi if [ -n "${fallback_function}" ]; then echo "(*) Trying ${fallback_name:-alternate version source}." >&2 local fallback_output="" @@ -61,8 +62,11 @@ find_version_from_git_tags() { fi local resolved_version="" - resolved_version="$(_select_requested_version "${requested_version}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${requested_version}" "${known_good_version}"; then + resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" + if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then + known_good_version="$(_known_good_version_for_repository "${repository}")" + fi + if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 fi diff --git a/test/_global/version-resolution/test.sh b/test/_global/version-resolution/test.sh index 31dff2201..9f9c2463d 100644 --- a/test/_global/version-resolution/test.sh +++ b/test/_global/version-resolution/test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC2317,SC2329 set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" @@ -14,7 +15,6 @@ git() { return 1 } -# shellcheck disable=SC2329 fallback_versions() { printf '%s\n' "1.4.2" "1.3.9" } @@ -42,6 +42,18 @@ if [ "${VERSION}" != "3.2.1" ] || grep -q "Trying" /tmp/version-resolution-outpu exit 1 fi +rm -f /tmp/version-resolution-network-call +git() { + touch /tmp/version-resolution-network-call + return 1 +} +VERSION="v1.33.0" +find_version_from_git_tags VERSION https://github.com/kubernetes/kubernetes >/tmp/version-resolution-output.log 2>&1 +if [ "${VERSION}" != "1.33.0" ] || [ -e /tmp/version-resolution-network-call ]; then + echo "Prefixed exact versions must normalize without remote resolution." >&2 + exit 1 +fi + git() { network_calls=$((network_calls + 1)) return 1 From 24dc8f59792ec75ff782bc580e62eea89f12a6ef Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 22 Sep 2026 05:35:48 +0000 Subject: [PATCH 3/4] Further changes for better maintainability --- scripts/version-resolution.sh | 37 +---- src/copilot-cli/install.sh | 3 +- src/copilot-cli/scripts/version-resolution.sh | 37 +---- src/docker-in-docker/install.sh | 9 +- .../scripts/version-resolution.sh | 37 +---- src/docker-outside-of-docker/install.sh | 6 +- .../scripts/version-resolution.sh | 37 +---- src/git-lfs/install.sh | 5 +- src/git-lfs/scripts/version-resolution.sh | 37 +---- src/github-cli/install.sh | 5 +- src/github-cli/scripts/version-resolution.sh | 37 +---- src/go/install.sh | 5 +- src/go/scripts/version-resolution.sh | 37 +---- src/kubectl-helm-minikube/install.sh | 10 +- .../scripts/version-resolution.sh | 37 +---- src/nix/install.sh | 3 +- src/nix/scripts/version-resolution.sh | 37 +---- src/node/install.sh | 3 +- src/node/scripts/version-resolution.sh | 37 +---- src/php/install.sh | 6 +- src/php/scripts/version-resolution.sh | 37 +---- src/powershell/install.sh | 6 +- src/powershell/scripts/version-resolution.sh | 37 +---- src/python/install.sh | 9 +- src/python/scripts/version-resolution.sh | 37 +---- src/rust/install.sh | 3 +- src/rust/scripts/version-resolution.sh | 37 +---- src/terraform/install.sh | 18 ++- src/terraform/scripts/version-resolution.sh | 37 +---- test/_global/version-resolution/cases.sh | 1 - test/_global/version-resolution/test.sh | 137 +++++++++++++----- 31 files changed, 206 insertions(+), 578 deletions(-) diff --git a/scripts/version-resolution.sh b/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/scripts/version-resolution.sh +++ b/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/copilot-cli/install.sh b/src/copilot-cli/install.sh index 6c34e8c87..f507ae8e3 100755 --- a/src/copilot-cli/install.sh +++ b/src/copilot-cli/install.sh @@ -9,6 +9,7 @@ CLI_VERSION=${VERSION:-"latest"} REQUESTED_CLI_VERSION="${CLI_VERSION}" +COPILOT_CLI_LAST_KNOWN_VERSION="1.0.87-0" set -e @@ -66,7 +67,7 @@ install_using_github() { if [ "${CLI_VERSION}" = "latest" ]; then download_from_github "https://github.com/github/copilot-cli/releases/latest/download/${cli_filename}" elif [ "${CLI_VERSION}" = "prerelease" ]; then - find_version_from_git_tags CLI_VERSION "https://github.com/github/copilot-cli" "tags/v" "." "false" "(-[0-9]+)" + find_version_from_git_tags CLI_VERSION "https://github.com/github/copilot-cli" "tags/v" "." "false" "(-[0-9]+)" "${COPILOT_CLI_LAST_KNOWN_VERSION}" download_from_github "https://github.com/github/copilot-cli/releases/download/v${CLI_VERSION}/${cli_filename}" else diff --git a/src/copilot-cli/scripts/version-resolution.sh b/src/copilot-cli/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/copilot-cli/scripts/version-resolution.sh +++ b/src/copilot-cli/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index fe2018a59..af45b197b 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -12,6 +12,9 @@ DOCKER_VERSION="${VERSION:-"latest"}" # The Docker/Moby Engine + CLI should matc USE_MOBY="${MOBY:-"true"}" MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}" DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"latest"}" #v1, v2, latest or none +DOCKER_COMPOSE_LAST_KNOWN_VERSION="5.5.1" +DOCKER_COMPOSE_SWITCH_LAST_KNOWN_VERSION="1.0.5" +DOCKER_BUILDX_LAST_KNOWN_VERSION="0.37.1" AZURE_DNS_AUTO_DETECTION="${AZUREDNSAUTODETECTION:-"true"}" DOCKER_DEFAULT_ADDRESS_POOL="${DOCKERDEFAULTADDRESSPOOL:-""}" DOCKER_HOST_GATEWAY_IP="${DOCKERHOSTGATEWAYIP:-""}" @@ -772,7 +775,7 @@ if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then else compose_version=${DOCKER_DASH_COMPOSE_VERSION#v} docker_compose_url="https://github.com/docker/compose" - find_version_from_git_tags compose_version "$docker_compose_url" "tags/v" + find_version_from_git_tags compose_version "$docker_compose_url" "tags/v" "." "false" "" "${DOCKER_COMPOSE_LAST_KNOWN_VERSION}" echo "(*) Installing docker-compose ${compose_version}..." curl -fsSL "https://github.com/docker/compose/releases/download/v${compose_version}/docker-compose-linux-${target_compose_arch}" -o ${docker_compose_path} || { echo -e "\n(!) Failed to fetch the latest artifacts for docker-compose v${compose_version}..." @@ -810,7 +813,7 @@ if [ "${INSTALL_DOCKER_COMPOSE_SWITCH}" = "true" ] && ! type compose-switch > /d compose_switch_url="https://github.com/docker/compose-switch" # Try to get latest version, fallback to known stable version if GitHub API fails set +e - find_version_from_git_tags compose_switch_version "$compose_switch_url" + find_version_from_git_tags compose_switch_version "$compose_switch_url" "tags/v" "." "false" "" "${DOCKER_COMPOSE_SWITCH_LAST_KNOWN_VERSION}" if [ $? -ne 0 ] || [ -z "${compose_switch_version}" ] || [ "${compose_switch_version}" = "latest" ]; then echo "(*) GitHub API rate limited or failed, using fallback method" fallback_compose-switch "$compose_switch_url" @@ -864,7 +867,7 @@ fallback_buildx() { if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then buildx_version="latest" docker_buildx_url="https://github.com/docker/buildx" - find_version_from_git_tags buildx_version "$docker_buildx_url" "refs/tags/v" + find_version_from_git_tags buildx_version "$docker_buildx_url" "refs/tags/v" "." "false" "" "${DOCKER_BUILDX_LAST_KNOWN_VERSION}" echo "(*) Installing buildx ${buildx_version}..." # Map architecture for buildx downloads diff --git a/src/docker-in-docker/scripts/version-resolution.sh b/src/docker-in-docker/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/docker-in-docker/scripts/version-resolution.sh +++ b/src/docker-in-docker/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index 62ab49e23..b883227c0 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -11,6 +11,8 @@ DOCKER_VERSION="${VERSION:-"latest"}" USE_MOBY="${MOBY:-"true"}" MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}" DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"latest"}" # v1 or v2 or none or latest +DOCKER_COMPOSE_LAST_KNOWN_VERSION="5.5.1" +DOCKER_COMPOSE_SWITCH_LAST_KNOWN_VERSION="1.0.5" ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}" SOCKET_PATH="${SOCKETPATH:-"/var/run/docker-host.sock"}" # From feature option @@ -385,7 +387,7 @@ if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then else compose_version=${DOCKER_DASH_COMPOSE_VERSION#v} docker_compose_url="https://github.com/docker/compose" - find_version_from_git_tags compose_version "$docker_compose_url" "tags/v" + find_version_from_git_tags compose_version "$docker_compose_url" "tags/v" "." "false" "" "${DOCKER_COMPOSE_LAST_KNOWN_VERSION}" echo "(*) Installing docker-compose ${compose_version}..." curl -fsSL "https://github.com/docker/compose/releases/download/v${compose_version}/docker-compose-linux-${target_compose_arch}" -o ${docker_compose_path} || { install_compose_fallback "$docker_compose_url" "$compose_version" "$target_compose_arch" "$docker_compose_path" @@ -410,7 +412,7 @@ if [ "${INSTALL_DOCKER_COMPOSE_SWITCH}" = "true" ] && ! type compose-switch > /d target_compose_path="$(dirname "${current_compose_path}")/docker-compose-v1" compose_switch_version="latest" compose_switch_url="https://github.com/docker/compose-switch" - find_version_from_git_tags compose_switch_version "${compose_switch_url}" + find_version_from_git_tags compose_switch_version "${compose_switch_url}" "tags/v" "." "false" "" "${DOCKER_COMPOSE_SWITCH_LAST_KNOWN_VERSION}" curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch || install_compose_switch_fallback "${compose_switch_url}" chmod +x /usr/local/bin/compose-switch # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11 diff --git a/src/docker-outside-of-docker/scripts/version-resolution.sh b/src/docker-outside-of-docker/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/docker-outside-of-docker/scripts/version-resolution.sh +++ b/src/docker-outside-of-docker/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/git-lfs/install.sh b/src/git-lfs/install.sh index 8b5bd16e9..f806f1258 100755 --- a/src/git-lfs/install.sh +++ b/src/git-lfs/install.sh @@ -10,6 +10,7 @@ GIT_LFS_VERSION=${VERSION:-"latest"} AUTO_PULL=${AUTOPULL:="true"} INSTALL_WITH_GITHUB=${INSTALLDIRECTLYFROMGITHUBRELEASE:="false"} +GIT_LFS_LAST_KNOWN_VERSION="3.8.0" GIT_LFS_ARCHIVE_GPG_KEY_URI="https://packagecloud.io/github/git-lfs/gpgkey" GIT_LFS_ARCHIVE_ARCHITECTURES="amd64 arm64" @@ -150,7 +151,7 @@ check_packages() { install_using_apt() { # Soft version matching if [ "${GIT_LFS_VERSION}" != "latest" ] && [ "${GIT_LFS_VERSION}" != "lts" ] && [ "${GIT_LFS_VERSION}" != "stable" ]; then - find_version_from_git_tags GIT_LFS_VERSION "https://github.com/git-lfs/git-lfs" + find_version_from_git_tags GIT_LFS_VERSION "https://github.com/git-lfs/git-lfs" "tags/v" "." "false" "" "${GIT_LFS_LAST_KNOWN_VERSION}" version_suffix="=${GIT_LFS_VERSION}" else version_suffix="" @@ -184,7 +185,7 @@ install_using_github() { echo "(*) No apt package for ${VERSION_CODENAME} ${architecture}. Installing manually." mkdir -p /tmp/git-lfs cd /tmp/git-lfs - find_version_from_git_tags GIT_LFS_VERSION "https://github.com/git-lfs/git-lfs" + find_version_from_git_tags GIT_LFS_VERSION "https://github.com/git-lfs/git-lfs" "tags/v" "." "false" "" "${GIT_LFS_LAST_KNOWN_VERSION}" install_from_release if grep -q "Not Found" "${git_lfs_filename}"; then diff --git a/src/git-lfs/scripts/version-resolution.sh b/src/git-lfs/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/git-lfs/scripts/version-resolution.sh +++ b/src/git-lfs/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/github-cli/install.sh b/src/github-cli/install.sh index 2d6dc9148..5f1ecf5a6 100755 --- a/src/github-cli/install.sh +++ b/src/github-cli/install.sh @@ -10,6 +10,7 @@ CLI_VERSION=${VERSION:-"latest"} INSTALL_DIRECTLY_FROM_GITHUB_RELEASE=${INSTALLDIRECTLYFROMGITHUBRELEASE:-"true"} EXTENSIONS=${EXTENSIONS:-""} +GITHUB_CLI_LAST_KNOWN_VERSION="2.101.0" GITHUB_CLI_ARCHIVE_GPG_KEY=7F38BBB59D064DBCB3D84D725612B36462313325 @@ -191,7 +192,7 @@ install_deb_using_github() { check_packages wget arch=$(dpkg --print-architecture) - find_version_from_git_tags CLI_VERSION https://github.com/cli/cli + find_version_from_git_tags CLI_VERSION https://github.com/cli/cli "tags/v" "." "false" "" "${GITHUB_CLI_LAST_KNOWN_VERSION}" cli_filename="gh_${CLI_VERSION}_linux_${arch}.deb" mkdir -p /tmp/ghcli @@ -223,7 +224,7 @@ fi # Soft version matching if [ "${CLI_VERSION}" != "latest" ] && [ "${CLI_VERSION}" != "lts" ] && [ "${CLI_VERSION}" != "stable" ]; then - find_version_from_git_tags CLI_VERSION "https://github.com/cli/cli" + find_version_from_git_tags CLI_VERSION "https://github.com/cli/cli" "tags/v" "." "false" "" "${GITHUB_CLI_LAST_KNOWN_VERSION}" version_suffix="=${CLI_VERSION}" else version_suffix="" diff --git a/src/github-cli/scripts/version-resolution.sh b/src/github-cli/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/github-cli/scripts/version-resolution.sh +++ b/src/github-cli/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/go/install.sh b/src/go/install.sh index 545d7ab9a..e7e0197d5 100755 --- a/src/go/install.sh +++ b/src/go/install.sh @@ -9,6 +9,7 @@ TARGET_GO_VERSION="${VERSION:-"latest"}" GOLANGCILINT_VERSION="${GOLANGCILINTVERSION:-"latest"}" +GO_LAST_KNOWN_VERSION="1.27.1" TARGET_GOROOT="${TARGET_GOROOT:-"/usr/local/go"}" TARGET_GOPATH="${TARGET_GOPATH:-"/go"}" @@ -228,7 +229,7 @@ if ! [ -f /usr/bin/find ]; then fi # Get closest match for version number specified -find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" "" "the official Go release index" get_go_release_versions "1.27.1" +find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" "" "${GO_LAST_KNOWN_VERSION}" "the official Go release index" get_go_release_versions architecture="$(uname -m)" case $architecture in @@ -271,7 +272,7 @@ if [[ "${TARGET_GO_VERSION}" != "none" ]] && [[ "$(go version 2>/dev/null)" != * ((minor=minor-1)) TARGET_GO_VERSION="${major}.${minor}" # Look for latest version from previous minor release - find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" "" "the official Go release index" get_go_release_versions "1.27.1" + find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true" "" "${GO_LAST_KNOWN_VERSION}" "the official Go release index" get_go_release_versions else ((breakfix=breakfix-1)) if [ "${breakfix}" = "0" ]; then diff --git a/src/go/scripts/version-resolution.sh b/src/go/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/go/scripts/version-resolution.sh +++ b/src/go/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/kubectl-helm-minikube/install.sh b/src/kubectl-helm-minikube/install.sh index 031955c1e..da7d82d13 100755 --- a/src/kubectl-helm-minikube/install.sh +++ b/src/kubectl-helm-minikube/install.sh @@ -19,6 +19,10 @@ KUBECTL_VERSION="${VERSION:-"latest"}" HELM_VERSION="${HELM:-"latest"}" MINIKUBE_VERSION="${MINIKUBE:-"latest"}" # latest is also valid +KUBECTL_LAST_KNOWN_VERSION="1.37.0" +HELM_LAST_KNOWN_VERSION="4.3.0" +MINIKUBE_LAST_KNOWN_VERSION="1.39.0" + KUBECTL_SHA256="${KUBECTL_SHA256:-"automatic"}" HELM_SHA256="${HELM_SHA256:-"automatic"}" MINIKUBE_SHA256="${MINIKUBE_SHA256:-"automatic"}" @@ -179,7 +183,7 @@ if [ ${KUBECTL_VERSION} != "none" ]; then KUBECTL_VERSION="${KUBECTL_FALLBACK_VERSION}" fi else - find_version_from_git_tags KUBECTL_VERSION https://github.com/kubernetes/kubernetes + find_version_from_git_tags KUBECTL_VERSION https://github.com/kubernetes/kubernetes "tags/v" "." "false" "" "${KUBECTL_LAST_KNOWN_VERSION}" fi if [ "${KUBECTL_VERSION::1}" != 'v' ]; then KUBECTL_VERSION="v${KUBECTL_VERSION}" @@ -280,7 +284,7 @@ if [ ${HELM_VERSION} != "none" ]; then # Install Helm, verify signature and checksum echo "Downloading Helm..." helm_url="https://github.com/helm/helm" - find_version_from_git_tags HELM_VERSION "${helm_url}" + find_version_from_git_tags HELM_VERSION "${helm_url}" "tags/v" "." "false" "" "${HELM_LAST_KNOWN_VERSION}" if [ "${HELM_VERSION::1}" != 'v' ]; then HELM_VERSION="v${HELM_VERSION}" fi @@ -343,7 +347,7 @@ if [ "${MINIKUBE_VERSION}" != "none" ]; then if [ "${MINIKUBE_VERSION}" = "latest" ] || [ "${MINIKUBE_VERSION}" = "lts" ] || [ "${MINIKUBE_VERSION}" = "current" ] || [ "${MINIKUBE_VERSION}" = "stable" ]; then MINIKUBE_VERSION="latest" else - find_version_from_git_tags MINIKUBE_VERSION https://github.com/kubernetes/minikube + find_version_from_git_tags MINIKUBE_VERSION https://github.com/kubernetes/minikube "tags/v" "." "false" "" "${MINIKUBE_LAST_KNOWN_VERSION}" if [ "${MINIKUBE_VERSION::1}" != "v" ]; then MINIKUBE_VERSION="v${MINIKUBE_VERSION}" fi diff --git a/src/kubectl-helm-minikube/scripts/version-resolution.sh b/src/kubectl-helm-minikube/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/kubectl-helm-minikube/scripts/version-resolution.sh +++ b/src/kubectl-helm-minikube/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/nix/install.sh b/src/nix/install.sh index f66943489..c37b56d14 100755 --- a/src/nix/install.sh +++ b/src/nix/install.sh @@ -12,6 +12,7 @@ USEATTRIBUTEPATH="${USEATTRIBUTEPATH:-"false"}" FLAKEURI="${FLAKEURI:-""}" EXTRANIXCONFIG="${EXTRANIXCONFIG:-""}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" +NIX_LAST_KNOWN_VERSION="2.35.2" if [ "$(id -u)" -ne 0 ]; then echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' @@ -39,7 +40,7 @@ check_command git git git git check_command xargs findutils findutils findutils # Determine version -find_version_from_git_tags VERSION https://github.com/NixOS/nix "tags/" +find_version_from_git_tags VERSION https://github.com/NixOS/nix "tags/" "." "false" "" "${NIX_LAST_KNOWN_VERSION}" # Download and verify install per https://nixos.org/download.html#nix-verify-installation tmpdir="$(mktemp -d)" diff --git a/src/nix/scripts/version-resolution.sh b/src/nix/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/nix/scripts/version-resolution.sh +++ b/src/nix/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/node/install.sh b/src/node/install.sh index e10a4a044..3765c98ac 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -11,6 +11,7 @@ export NODE_VERSION="${VERSION:-"lts"}" export NPM_VERSION="${NPMVERSION:-"lts"}" export PNPM_VERSION="${PNPMVERSION:-"latest"}" export NVM_VERSION="${NVMVERSION:-"latest"}" +NVM_LAST_KNOWN_VERSION="0.40.8" export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}" INSTALL_TOOLS_FOR_NODE_GYP="${NODEGYPDEPENDENCIES:-true}" export INSTALL_YARN_USING_APT="${INSTALLYARNUSINGAPT:-false}" # only concerns Debian-based systems @@ -300,7 +301,7 @@ elif [ "${NODE_VERSION}" = "latest" ]; then export NODE_VERSION="node" fi -find_version_from_git_tags NVM_VERSION "https://github.com/nvm-sh/nvm" +find_version_from_git_tags NVM_VERSION "https://github.com/nvm-sh/nvm" "tags/v" "." "false" "" "${NVM_LAST_KNOWN_VERSION}" # Install snipppet that we will run as the user nvm_install_snippet="$(cat << EOF diff --git a/src/node/scripts/version-resolution.sh b/src/node/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/node/scripts/version-resolution.sh +++ b/src/node/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/php/install.sh b/src/php/install.sh index baa4fdedb..d1d0c1181 100755 --- a/src/php/install.sh +++ b/src/php/install.sh @@ -14,6 +14,8 @@ rm -rf /var/lib/apt/lists/* PHP_VERSION="${VERSION:-"latest"}" INSTALL_COMPOSER="${INSTALLCOMPOSER:-"true"}" OVERRIDE_DEFAULT_VERSION="${OVERRIDEDEFAULTVERSION:-"true"}" +PHP_LAST_KNOWN_VERSION="8.5.10" +XDEBUG_LAST_KNOWN_VERSION="3.5.3" export PHP_DIR="${PHP_DIR:-"/usr/local/php"}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" @@ -177,7 +179,7 @@ addcomposer() { # the current PHP version (common around new PHP releases). install_xdebug_from_source() { XDEBUG_VERSION="latest" - find_version_from_git_tags XDEBUG_VERSION https://github.com/xdebug/xdebug "tags/" + find_version_from_git_tags XDEBUG_VERSION https://github.com/xdebug/xdebug "tags/" "." "false" "" "${XDEBUG_LAST_KNOWN_VERSION}" local xdebug_src_dir="/tmp/xdebug-src" rm -rf "${xdebug_src_dir}" @@ -307,7 +309,7 @@ if [ "${PHP_VERSION}" != "none" ]; then # storing value of PHP_VERSION before it changes ORIGINAL_PHP_VERSION=$PHP_VERSION - find_version_from_git_tags PHP_VERSION https://github.com/php/php-src "tags/php-" + find_version_from_git_tags PHP_VERSION https://github.com/php/php-src "tags/php-" "." "false" "" "${PHP_LAST_KNOWN_VERSION}" install_php "${PHP_VERSION}" PHP_SRC="${PHP_INSTALL_DIR}/bin/php" diff --git a/src/php/scripts/version-resolution.sh b/src/php/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/php/scripts/version-resolution.sh +++ b/src/php/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/powershell/install.sh b/src/powershell/install.sh index 2764cf3f5..ea81c509a 100755 --- a/src/powershell/install.sh +++ b/src/powershell/install.sh @@ -19,6 +19,8 @@ POWERSHELL_VERSION=${VERSION:-"latest"} POWERSHELL_MODULES="${MODULES:-""}" POWERSHELL_PROFILE_URL="${POWERSHELLPROFILEURL}" PRESERVE_HISTORY="${PRESERVEHISTORY}" +POWERSHELL_STABLE_LAST_KNOWN_VERSION="7.6.6" +POWERSHELL_PREVIEW_LAST_KNOWN_VERSION="7.7.0-preview.4" MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" #MICROSOFT_GPG_KEYS_URI=$(curl https://packages.microsoft.com/keys/microsoft.asc -o /usr/share/keyrings/microsoft-archive-keyring.gpg) @@ -395,9 +397,9 @@ install_using_github() { # Check if we need to find a preview version or stable version if [[ "${POWERSHELL_VERSION}" == *"preview"* ]] || [ "${POWERSHELL_VERSION}" = "preview" ] || [[ "${POWERSHELL_VERSION}" == *"-rc."* ]]; then echo "Finding preview version..." - find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" "tags/v" "." "false" "(-preview\.[0-9]+|-rc\.[0-9]+)" "GitHub REST API" _github_rest_version_candidates "7.7.0-preview.4" + find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" "tags/v" "." "false" "(-preview\.[0-9]+|-rc\.[0-9]+)" "${POWERSHELL_PREVIEW_LAST_KNOWN_VERSION}" "GitHub REST API" _github_rest_version_candidates else - find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" + find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" "tags/v" "." "false" "" "${POWERSHELL_STABLE_LAST_KNOWN_VERSION}" fi install_pwsh "${POWERSHELL_VERSION}" diff --git a/src/powershell/scripts/version-resolution.sh b/src/powershell/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/powershell/scripts/version-resolution.sh +++ b/src/powershell/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/python/install.sh b/src/python/install.sh index aa4571bbc..8d3ff1f82 100755 --- a/src/python/install.sh +++ b/src/python/install.sh @@ -14,6 +14,9 @@ OPTIMIZE_BUILD_FROM_SOURCE="${OPTIMIZE:-"false"}" ENABLE_SHARED_FROM_SOURCE="${ENABLESHARED:-"false"}" PYTHON_INSTALL_PATH="${INSTALLPATH:-"/usr/local/python"}" OVERRIDE_DEFAULT_VERSION="${OVERRIDEDEFAULTVERSION:-"true"}" +PYTHON_LAST_KNOWN_VERSION="3.14.7" +OPENSSL3_LAST_KNOWN_VERSION="4.0.2" +COSIGN_LAST_KNOWN_VERSION="3.1.3" export PIPX_HOME=${PIPX_HOME:-"/usr/local/py-utils"} @@ -463,7 +466,7 @@ install_openssl3() { cd /tmp/openssl3 openssl3_version="3.0" # Find version using soft match - find_version_from_git_tags openssl3_version "https://github.com/openssl/openssl" "openssl-" + find_version_from_git_tags openssl3_version "https://github.com/openssl/openssl" "openssl-" "." "false" "" "${OPENSSL3_LAST_KNOWN_VERSION}" local tgz_filename="openssl-${openssl3_version}.tar.gz" local tgz_url="https://github.com/openssl/openssl/releases/download/openssl-${openssl3_version}/${tgz_filename}" echo "Downloading ${tgz_filename}..." @@ -521,7 +524,7 @@ install_cosign() { local cosign_url='https://github.com/sigstore/cosign' local architecture=$(get_architecture) - find_version_from_git_tags COSIGN_VERSION "${cosign_url}" + find_version_from_git_tags COSIGN_VERSION "${cosign_url}" "tags/v" "." "false" "" "${COSIGN_LAST_KNOWN_VERSION}" # Remove 'v' prefix if present for download URL local version_for_url="${COSIGN_VERSION#v}" @@ -634,7 +637,7 @@ install_from_source() { fi # Find version using soft match - find_version_from_git_tags VERSION "https://github.com/python/cpython" + find_version_from_git_tags VERSION "https://github.com/python/cpython" "tags/v" "." "false" "" "${PYTHON_LAST_KNOWN_VERSION}" # Some platforms/os versions need modern versions of openssl installed # via common package repositories, for now rhel-7 family, use case statement to diff --git a/src/python/scripts/version-resolution.sh b/src/python/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/python/scripts/version-resolution.sh +++ b/src/python/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/rust/install.sh b/src/rust/install.sh index ee295ff12..cfa01b624 100755 --- a/src/rust/install.sh +++ b/src/rust/install.sh @@ -10,6 +10,7 @@ RUST_VERSION="${VERSION:-"latest"}" RUSTUP_PROFILE="${PROFILE:-"minimal"}" RUSTUP_TARGETS="${TARGETS:-""}" +RUST_LAST_KNOWN_VERSION="1.98.1" # Set to "none" to install no components beyond the selected profile. RUSTUP_COMPONENTS="${COMPONENTS:-rust-analyzer,rust-src,rustfmt,clippy}" IFS=',' read -ra components <<< "${RUSTUP_COMPONENTS}" @@ -377,7 +378,7 @@ else if [ $is_nightly = 0 ]; then check_nightly_version_formatting RUST_VERSION else - find_version_from_git_tags RUST_VERSION "https://github.com/rust-lang/rust" "tags/" + find_version_from_git_tags RUST_VERSION "https://github.com/rust-lang/rust" "tags/" "." "false" "" "${RUST_LAST_KNOWN_VERSION}" fi default_toolchain_arg="--default-toolchain ${RUST_VERSION}" fi diff --git a/src/rust/scripts/version-resolution.sh b/src/rust/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/rust/scripts/version-resolution.sh +++ b/src/rust/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/src/terraform/install.sh b/src/terraform/install.sh index 0b29a5fbd..68cff24b9 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -15,6 +15,12 @@ rm -rf /var/lib/apt/lists/* TERRAFORM_VERSION="${VERSION:-"latest"}" TFLINT_VERSION="${TFLINT:-"latest"}" TERRAGRUNT_VERSION="${TERRAGRUNT:-"latest"}" +TERRAFORM_LAST_KNOWN_VERSION="1.16.3" +TFLINT_LAST_KNOWN_VERSION="0.64.0" +TERRAGRUNT_LAST_KNOWN_VERSION="1.1.5" +TFSEC_LAST_KNOWN_VERSION="1.28.14" +TERRAFORM_DOCS_LAST_KNOWN_VERSION="0.24.0" +COSIGN_LAST_KNOWN_VERSION="3.1.3" INSTALL_SENTINEL=${INSTALLSENTINEL:-false} INSTALL_TFSEC=${INSTALLTFSEC:-false} INSTALL_TERRAFORM_DOCS=${INSTALLTERRAFORMDOCS:-false} @@ -360,7 +366,7 @@ ensure_cosign() { echo "Installing cosign..." COSIGN_VERSION="latest" cosign_url='https://github.com/sigstore/cosign' - find_version_from_git_tags COSIGN_VERSION "${cosign_url}" + find_version_from_git_tags COSIGN_VERSION "${cosign_url}" "tags/v" "." "false" "" "${COSIGN_LAST_KNOWN_VERSION}" install_cosign "${COSIGN_VERSION}" "${cosign_url}" fi if ! type cosign > /dev/null 2>&1; then @@ -383,9 +389,9 @@ terraform_url='https://github.com/hashicorp/terraform' tflint_url='https://github.com/terraform-linters/tflint' terragrunt_url='https://github.com/gruntwork-io/terragrunt' # Verify requested version is available, convert latest -find_version_from_git_tags TERRAFORM_VERSION "$terraform_url" -find_version_from_git_tags TFLINT_VERSION "$tflint_url" -find_version_from_git_tags TERRAGRUNT_VERSION "$terragrunt_url" +find_version_from_git_tags TERRAFORM_VERSION "$terraform_url" "tags/v" "." "false" "" "${TERRAFORM_LAST_KNOWN_VERSION}" +find_version_from_git_tags TFLINT_VERSION "$tflint_url" "tags/v" "." "false" "" "${TFLINT_LAST_KNOWN_VERSION}" +find_version_from_git_tags TERRAGRUNT_VERSION "$terragrunt_url" "tags/v" "." "false" "" "${TERRAGRUNT_LAST_KNOWN_VERSION}" install_terraform() { local TERRAFORM_VERSION=$1 @@ -615,7 +621,7 @@ install_tfsec() { if [ "${INSTALL_TFSEC}" = "true" ]; then TFSEC_VERSION="latest" tfsec_url='https://github.com/aquasecurity/tfsec' - find_version_from_git_tags TFSEC_VERSION $tfsec_url + find_version_from_git_tags TFSEC_VERSION "$tfsec_url" "tags/v" "." "false" "" "${TFSEC_LAST_KNOWN_VERSION}" tfsec_filename="tfsec_${TFSEC_VERSION}_linux_${architecture}.tar.gz" echo "(*) Downloading TFSec... ${tfsec_filename}" install_tfsec "$TFSEC_VERSION" @@ -645,7 +651,7 @@ install_terraform_docs() { if [ "${INSTALL_TERRAFORM_DOCS}" = "true" ]; then terraform_docs_url='https://github.com/terraform-docs/terraform-docs' - find_version_from_git_tags TERRAFORM_DOCS_VERSION $terraform_docs_url + find_version_from_git_tags TERRAFORM_DOCS_VERSION "$terraform_docs_url" "tags/v" "." "false" "" "${TERRAFORM_DOCS_LAST_KNOWN_VERSION}" tfdocs_filename="terraform-docs-v${TERRAFORM_DOCS_VERSION}-linux-${architecture}.tar.gz" echo "(*) Downloading Terraform docs... ${tfdocs_filename}" install_terraform_docs "$TERRAFORM_DOCS_VERSION" diff --git a/src/terraform/scripts/version-resolution.sh b/src/terraform/scripts/version-resolution.sh index 2b2ff3045..d1b9d5f81 100644 --- a/src/terraform/scripts/version-resolution.sh +++ b/src/terraform/scripts/version-resolution.sh @@ -10,9 +10,9 @@ find_version_from_git_tags() { local separator=${4:-"."} local last_part_optional=${5:-"false"} local version_suffix_regex=${6:-""} - local fallback_name=${7:-""} - local fallback_function=${8:-""} - local known_good_version=${9:-""} + local known_good_version=${7:-""} + local fallback_name=${8:-""} + local fallback_function=${9:-""} if [ "${requested_version}" = "none" ]; then return @@ -63,9 +63,6 @@ find_version_from_git_tags() { local resolved_version="" resolved_version="$(_select_requested_version "${normalized_request}" "${version_list}")" - if [ -z "${resolved_version}" ] && [ -z "${known_good_version}" ]; then - known_good_version="$(_known_good_version_for_repository "${repository}")" - fi if [ -z "${resolved_version}" ] && [ -n "${known_good_version}" ] && _version_matches_request "${normalized_request}" "${known_good_version}"; then resolved_version="${known_good_version}" echo "(!) Dynamic version resolution failed; using known-good version ${known_good_version}." >&2 @@ -94,34 +91,6 @@ _github_rest_version_candidates() { | sed "s|^${tag_prefix}||" } -_known_good_version_for_repository() { - case ${1%/} in - https://github.com/docker/compose) echo "5.5.1" ;; - https://github.com/docker/compose-switch) echo "1.0.5" ;; - https://github.com/docker/buildx) echo "0.37.1" ;; - https://github.com/cli/cli) echo "2.101.0" ;; - https://github.com/git-lfs/git-lfs) echo "3.8.0" ;; - https://github.com/helm/helm) echo "4.3.0" ;; - https://github.com/kubernetes/kubernetes) echo "1.37.0" ;; - https://github.com/kubernetes/minikube) echo "1.39.0" ;; - https://github.com/NixOS/nix) echo "2.35.2" ;; - https://github.com/nvm-sh/nvm) echo "0.40.8" ;; - https://github.com/php/php-src) echo "8.5.10" ;; - https://github.com/xdebug/xdebug) echo "3.5.3" ;; - https://github.com/PowerShell/PowerShell) echo "7.6.6" ;; - https://github.com/python/cpython) echo "3.14.7" ;; - https://github.com/openssl/openssl) echo "4.0.2" ;; - https://github.com/sigstore/cosign) echo "3.1.3" ;; - https://github.com/rust-lang/rust) echo "1.98.1" ;; - https://github.com/hashicorp/terraform) echo "1.16.3" ;; - https://github.com/terraform-linters/tflint) echo "0.64.0" ;; - https://github.com/gruntwork-io/terragrunt) echo "1.1.5" ;; - https://github.com/aquasecurity/tfsec) echo "1.28.14" ;; - https://github.com/terraform-docs/terraform-docs) echo "0.24.0" ;; - https://github.com/github/copilot-cli) echo "1.0.87-0" ;; - esac -} - _extract_version_candidates() { local input=$1 local prefix=$2 diff --git a/test/_global/version-resolution/cases.sh b/test/_global/version-resolution/cases.sh index db3cc39ce..e5aee85ff 100644 --- a/test/_global/version-resolution/cases.sh +++ b/test/_global/version-resolution/cases.sh @@ -19,7 +19,6 @@ VERSION_RESOLUTION_CASES=( "php|xdebug|https://github.com/xdebug/xdebug|tags/|false||latest|3.5.3|3.5.3" "powershell|stable|https://github.com/PowerShell/PowerShell|tags/v|false||latest|v7.6.6|7.6.6" "powershell|preview|https://github.com/PowerShell/PowerShell|tags/v|false|-preview\\.[0-9]+|preview|v7.7.0-preview.4|7.7.0-preview.4" - "powershell|release candidate|https://github.com/PowerShell/PowerShell|tags/v|false|-rc\\.[0-9]+|prerelease|v7.7.0-rc.1|7.7.0-rc.1" "python|cpython|https://github.com/python/cpython|tags/v|false||latest|v3.14.7|3.14.7" "python|openssl|https://github.com/openssl/openssl|openssl-|false||latest|openssl-4.0.2|4.0.2" "python|cosign|https://github.com/sigstore/cosign|tags/v|false||latest|v3.1.3|3.1.3" diff --git a/test/_global/version-resolution/test.sh b/test/_global/version-resolution/test.sh index 9f9c2463d..cbb6da2ca 100644 --- a/test/_global/version-resolution/test.sh +++ b/test/_global/version-resolution/test.sh @@ -9,6 +9,21 @@ source "$(dirname "${BASH_SOURCE[0]}")/cases.sh" bash "${ROOT_DIR}/scripts/sync-version-resolution.sh" --check +passed=0 + +pass() { + passed=$((passed + 1)) + printf 'ok %d - %s\n' "${passed}" "$1" +} + +fail() { + printf 'not ok %d - %s\n' "$((passed + 1))" "$1" >&2 + if [ -s /tmp/version-resolution-output.log ]; then + cat /tmp/version-resolution-output.log >&2 + fi + exit 1 +} + network_calls=0 git() { network_calls=$((network_calls + 1)) @@ -26,9 +41,9 @@ curl() { VERSION="1.2.3" find_version_from_git_tags VERSION https://github.com/example/tool if [ "${VERSION}" != "1.2.3" ] || [ "${network_calls}" -ne 0 ]; then - echo "Exact versions must not perform remote resolution." >&2 - exit 1 + fail "exact versions avoid remote resolution" fi +pass "exact versions avoid remote resolution" git() { printf '%s\n' \ @@ -38,9 +53,9 @@ git() { VERSION="3.2" find_version_from_git_tags VERSION https://github.com/example/tool >/tmp/version-resolution-output.log 2>&1 if [ "${VERSION}" != "3.2.1" ] || grep -q "Trying" /tmp/version-resolution-output.log; then - echo "Primary git tag resolution did not select the requested version line." >&2 - exit 1 + fail "primary git tags select the requested version line" fi +pass "primary git tags select the requested version line" rm -f /tmp/version-resolution-network-call git() { @@ -50,9 +65,9 @@ git() { VERSION="v1.33.0" find_version_from_git_tags VERSION https://github.com/kubernetes/kubernetes >/tmp/version-resolution-output.log 2>&1 if [ "${VERSION}" != "1.33.0" ] || [ -e /tmp/version-resolution-network-call ]; then - echo "Prefixed exact versions must normalize without remote resolution." >&2 - exit 1 + fail "prefixed exact versions normalize without remote resolution" fi +pass "prefixed exact versions normalize without remote resolution" git() { network_calls=$((network_calls + 1)) @@ -60,67 +75,91 @@ git() { } VERSION="latest" -find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'example releases API' fallback_versions 1.2.3 >/tmp/version-resolution-output.log 2>&1 +find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 1.2.3 'example releases API' fallback_versions >/tmp/version-resolution-output.log 2>&1 if [ "${VERSION}" != "1.4.2" ]; then - echo "Fallback resolver did not select the latest version." >&2 - exit 1 + fail "alternate resolver selects the latest version" fi if ! grep -q "Trying example releases API" /tmp/version-resolution-output.log; then - echo "Fallback warning did not identify the alternate source." >&2 - exit 1 + fail "alternate resolver warning identifies its source" fi +pass "alternate resolver selects the latest version and reports its source" fallback_versions() { return 1 } VERSION="1.2" -find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'example releases API' fallback_versions 1.2.3 >/tmp/version-resolution-output.log 2>&1 +find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 1.2.3 'example releases API' fallback_versions >/tmp/version-resolution-output.log 2>&1 if [ "${VERSION}" != "1.2.3" ] || ! grep -q "known-good version 1.2.3" /tmp/version-resolution-output.log; then - echo "Compatible known-good fallback was not selected or reported." >&2 - exit 1 + fail "compatible known-good fallback is selected and reported" fi +pass "compatible known-good fallback is selected and reported" VERSION="2.0" -if find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'example releases API' fallback_versions 1.2.3 >/tmp/version-resolution-error.log 2>&1; then - echo "An incompatible known-good version must be rejected." >&2 - exit 1 +if find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 1.2.3 'example releases API' fallback_versions >/tmp/version-resolution-error.log 2>&1; then + fail "incompatible known-good versions are rejected" fi if ! grep -q "Specify an exact version" /tmp/version-resolution-error.log; then - echo "Final resolution error is not actionable." >&2 - exit 1 + fail "final resolution error is actionable" fi +pass "incompatible known-good versions are rejected with actionable errors" fallback_versions() { printf '%s\n' "not-a-version" "release-next" } VERSION="latest" -if find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' 'malformed source' fallback_versions '' >/tmp/version-resolution-error.log 2>&1; then - echo "Malformed fallback versions must be rejected." >&2 - exit 1 +if find_version_from_git_tags VERSION https://github.com/example/tool tags/v . false '' '' 'malformed source' fallback_versions >/tmp/version-resolution-error.log 2>&1; then + fail "malformed fallback versions are rejected" fi +pass "malformed fallback versions are rejected" VERSION="latest" find_version_from_git_tags VERSION https://github.com/example/unpinned >/tmp/version-resolution-output.log 2>&1 if [ "${VERSION}" != "2.3.4" ] || ! grep -q "GitHub REST API" /tmp/version-resolution-output.log; then - echo "Automatic GitHub REST fallback did not select and report a version." >&2 - exit 1 + fail "automatic GitHub REST fallback selects and reports a version" fi +pass "automatic GitHub REST fallback selects and reports a version" + +required_features=( + copilot-cli + docker-in-docker + docker-outside-of-docker + git-lfs + github-cli + go + kubectl-helm-minikube + nix + node + php + powershell + python + rust + terraform +) +declare -A covered_features=() +for test_case in "${VERSION_RESOLUTION_CASES[@]}"; do + IFS='|' read -r feature _ <<< "${test_case}" + covered_features["${feature}"]=true +done +for feature in "${required_features[@]}"; do + if [ "${covered_features[${feature}]:-false}" != "true" ]; then + fail "fallback matrix covers ${feature}" + fi +done +pass "fallback matrix covers all ${#required_features[@]} required features" for test_case in "${VERSION_RESOLUTION_CASES[@]}"; do IFS='|' read -r feature label repository prefix last_part_optional suffix_regex request fallback_tag expected <<< "${test_case}" - # shellcheck disable=SC1090 - source "${ROOT_DIR}/src/${feature}/scripts/version-resolution.sh" VERSION="${expected}" rm -f /tmp/version-resolution-network-call git() { touch /tmp/version-resolution-network-call return 1 } - find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" >/tmp/version-resolution-output.log 2>&1 + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" >/tmp/version-resolution-output.log 2>&1 if [ -e /tmp/version-resolution-network-call ]; then - echo "${feature} ${label}: exact version performed remote resolution." >&2 - exit 1 + fail "${feature}/${label}: exact version avoids remote resolution" fi + pass "${feature}/${label}: exact version avoids remote resolution" git() { return 1 @@ -131,18 +170,44 @@ for test_case in "${VERSION_RESOLUTION_CASES[@]}"; do go_fallback_versions() { printf '%s\n' "${FAKE_FALLBACK_TAG}" } - find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "the official Go release index" go_fallback_versions "${expected}" >/tmp/version-resolution-output.log 2>&1 + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" "the official Go release index" go_fallback_versions >/tmp/version-resolution-output.log 2>&1 else - find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" >/tmp/version-resolution-output.log 2>&1 + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" >/tmp/version-resolution-output.log 2>&1 fi if [ "${VERSION}" != "${expected}" ]; then - echo "${feature} ${label}: expected ${expected}, got ${VERSION}." >&2 - exit 1 + fail "${feature}/${label}: alternate source selects ${expected}" fi if ! grep -qE 'Trying (GitHub REST API|the official Go release index)' /tmp/version-resolution-output.log; then - echo "${feature} ${label}: fallback warning did not identify its source." >&2 - exit 1 + fail "${feature}/${label}: alternate-source warning identifies its source" + fi + pass "${feature}/${label}: alternate source selects ${expected} and is reported" + + git() { + return 1 + } + curl() { + return 1 + } + fallback_unavailable() { + return 1 + } + VERSION="${request}" + if [ "${feature}" = "go" ]; then + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" "the official Go release index" fallback_unavailable >/tmp/version-resolution-output.log 2>&1 + elif [ "${feature}" = "powershell" ] && [ "${label}" = "preview" ]; then + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" "GitHub REST API" fallback_unavailable >/tmp/version-resolution-output.log 2>&1 + else + find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" >/tmp/version-resolution-output.log 2>&1 + fi + if [ "${VERSION}" != "${expected}" ] || ! grep -q "known-good version ${expected}" /tmp/version-resolution-output.log; then + fail "${feature}/${label}: unavailable alternate source selects known-good ${expected}" fi + pass "${feature}/${label}: unavailable alternate source selects known-good ${expected}" + + curl() { + printf '[{"name":"%s"}]\n' "${FAKE_FALLBACK_TAG:-v2.3.4}" + } done -echo "Version resolution tests passed." \ No newline at end of file +printf '1..%d\n' "${passed}" +printf 'Version resolution tests passed: %d assertions.\n' "${passed}" \ No newline at end of file From 892d6ef0b3448587ccce4af8c40bafec72a18906 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 22 Sep 2026 05:58:39 +0000 Subject: [PATCH 4/4] Modify the test script --- test/_global/version-resolution/cases.sh | 56 ++++++++++++------------ test/_global/version-resolution/test.sh | 17 ++++++- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/test/_global/version-resolution/cases.sh b/test/_global/version-resolution/cases.sh index e5aee85ff..df7cbd29e 100644 --- a/test/_global/version-resolution/cases.sh +++ b/test/_global/version-resolution/cases.sh @@ -1,32 +1,32 @@ #!/usr/bin/env bash # shellcheck disable=SC2034 -# feature|label|repository|prefix|optional patch|suffix regex|request|fallback tag|expected +# feature|label|known-good variable|repository|prefix|optional patch|suffix regex|request VERSION_RESOLUTION_CASES=( - "docker-in-docker|compose|https://github.com/docker/compose|tags/v|false||latest|v5.5.1|5.5.1" - "docker-in-docker|compose switch|https://github.com/docker/compose-switch|tags/v|false||latest|v1.0.5|1.0.5" - "docker-in-docker|buildx|https://github.com/docker/buildx|refs/tags/v|false||latest|v0.37.1|0.37.1" - "docker-outside-of-docker|compose|https://github.com/docker/compose|tags/v|false||latest|v5.5.1|5.5.1" - "docker-outside-of-docker|compose switch|https://github.com/docker/compose-switch|tags/v|false||latest|v1.0.5|1.0.5" - "github-cli|gh|https://github.com/cli/cli|tags/v|false||latest|v2.101.0|2.101.0" - "git-lfs|git lfs|https://github.com/git-lfs/git-lfs|tags/v|false||latest|v3.8.0|3.8.0" - "go|go|https://go.googlesource.com/go|tags/go|true||latest|1.27.1|1.27.1" - "kubectl-helm-minikube|kubectl|https://github.com/kubernetes/kubernetes|tags/v|false||latest|v1.37.0|1.37.0" - "kubectl-helm-minikube|helm|https://github.com/helm/helm|tags/v|false||latest|v4.3.0|4.3.0" - "kubectl-helm-minikube|minikube|https://github.com/kubernetes/minikube|tags/v|false||latest|v1.39.0|1.39.0" - "nix|nix|https://github.com/NixOS/nix|tags/|false||latest|2.35.2|2.35.2" - "node|nvm|https://github.com/nvm-sh/nvm|tags/v|false||latest|v0.40.8|0.40.8" - "php|php|https://github.com/php/php-src|tags/php-|false||latest|php-8.5.10|8.5.10" - "php|xdebug|https://github.com/xdebug/xdebug|tags/|false||latest|3.5.3|3.5.3" - "powershell|stable|https://github.com/PowerShell/PowerShell|tags/v|false||latest|v7.6.6|7.6.6" - "powershell|preview|https://github.com/PowerShell/PowerShell|tags/v|false|-preview\\.[0-9]+|preview|v7.7.0-preview.4|7.7.0-preview.4" - "python|cpython|https://github.com/python/cpython|tags/v|false||latest|v3.14.7|3.14.7" - "python|openssl|https://github.com/openssl/openssl|openssl-|false||latest|openssl-4.0.2|4.0.2" - "python|cosign|https://github.com/sigstore/cosign|tags/v|false||latest|v3.1.3|3.1.3" - "rust|rust|https://github.com/rust-lang/rust|tags/|false||latest|1.98.1|1.98.1" - "copilot-cli|prerelease|https://github.com/github/copilot-cli|tags/v|false|(-[0-9]+)|prerelease|v1.0.87-0|1.0.87-0" - "terraform|terraform|https://github.com/hashicorp/terraform|tags/v|false||latest|v1.16.3|1.16.3" - "terraform|tflint|https://github.com/terraform-linters/tflint|tags/v|false||latest|v0.64.0|0.64.0" - "terraform|terragrunt|https://github.com/gruntwork-io/terragrunt|tags/v|false||latest|v1.1.5|1.1.5" - "terraform|tfsec|https://github.com/aquasecurity/tfsec|tags/v|false||latest|v1.28.14|1.28.14" - "terraform|terraform-docs|https://github.com/terraform-docs/terraform-docs|tags/v|false||latest|v0.24.0|0.24.0" + "docker-in-docker|compose|DOCKER_COMPOSE_LAST_KNOWN_VERSION|https://github.com/docker/compose|tags/v|false||latest" + "docker-in-docker|compose switch|DOCKER_COMPOSE_SWITCH_LAST_KNOWN_VERSION|https://github.com/docker/compose-switch|tags/v|false||latest" + "docker-in-docker|buildx|DOCKER_BUILDX_LAST_KNOWN_VERSION|https://github.com/docker/buildx|refs/tags/v|false||latest" + "docker-outside-of-docker|compose|DOCKER_COMPOSE_LAST_KNOWN_VERSION|https://github.com/docker/compose|tags/v|false||latest" + "docker-outside-of-docker|compose switch|DOCKER_COMPOSE_SWITCH_LAST_KNOWN_VERSION|https://github.com/docker/compose-switch|tags/v|false||latest" + "github-cli|gh|GITHUB_CLI_LAST_KNOWN_VERSION|https://github.com/cli/cli|tags/v|false||latest" + "git-lfs|git lfs|GIT_LFS_LAST_KNOWN_VERSION|https://github.com/git-lfs/git-lfs|tags/v|false||latest" + "go|go|GO_LAST_KNOWN_VERSION|https://go.googlesource.com/go|tags/go|true||latest" + "kubectl-helm-minikube|kubectl|KUBECTL_LAST_KNOWN_VERSION|https://github.com/kubernetes/kubernetes|tags/v|false||latest" + "kubectl-helm-minikube|helm|HELM_LAST_KNOWN_VERSION|https://github.com/helm/helm|tags/v|false||latest" + "kubectl-helm-minikube|minikube|MINIKUBE_LAST_KNOWN_VERSION|https://github.com/kubernetes/minikube|tags/v|false||latest" + "nix|nix|NIX_LAST_KNOWN_VERSION|https://github.com/NixOS/nix|tags/|false||latest" + "node|nvm|NVM_LAST_KNOWN_VERSION|https://github.com/nvm-sh/nvm|tags/v|false||latest" + "php|php|PHP_LAST_KNOWN_VERSION|https://github.com/php/php-src|tags/php-|false||latest" + "php|xdebug|XDEBUG_LAST_KNOWN_VERSION|https://github.com/xdebug/xdebug|tags/|false||latest" + "powershell|stable|POWERSHELL_STABLE_LAST_KNOWN_VERSION|https://github.com/PowerShell/PowerShell|tags/v|false||latest" + "powershell|preview|POWERSHELL_PREVIEW_LAST_KNOWN_VERSION|https://github.com/PowerShell/PowerShell|tags/v|false|-preview\\.[0-9]+|preview" + "python|cpython|PYTHON_LAST_KNOWN_VERSION|https://github.com/python/cpython|tags/v|false||latest" + "python|openssl|OPENSSL3_LAST_KNOWN_VERSION|https://github.com/openssl/openssl|openssl-|false||latest" + "python|cosign|COSIGN_LAST_KNOWN_VERSION|https://github.com/sigstore/cosign|tags/v|false||latest" + "rust|rust|RUST_LAST_KNOWN_VERSION|https://github.com/rust-lang/rust|tags/|false||latest" + "copilot-cli|prerelease|COPILOT_CLI_LAST_KNOWN_VERSION|https://github.com/github/copilot-cli|tags/v|false|(-[0-9]+)|prerelease" + "terraform|terraform|TERRAFORM_LAST_KNOWN_VERSION|https://github.com/hashicorp/terraform|tags/v|false||latest" + "terraform|tflint|TFLINT_LAST_KNOWN_VERSION|https://github.com/terraform-linters/tflint|tags/v|false||latest" + "terraform|terragrunt|TERRAGRUNT_LAST_KNOWN_VERSION|https://github.com/gruntwork-io/terragrunt|tags/v|false||latest" + "terraform|tfsec|TFSEC_LAST_KNOWN_VERSION|https://github.com/aquasecurity/tfsec|tags/v|false||latest" + "terraform|terraform-docs|TERRAFORM_DOCS_LAST_KNOWN_VERSION|https://github.com/terraform-docs/terraform-docs|tags/v|false||latest" ) \ No newline at end of file diff --git a/test/_global/version-resolution/test.sh b/test/_global/version-resolution/test.sh index cbb6da2ca..b947e9015 100644 --- a/test/_global/version-resolution/test.sh +++ b/test/_global/version-resolution/test.sh @@ -148,7 +148,20 @@ done pass "fallback matrix covers all ${#required_features[@]} required features" for test_case in "${VERSION_RESOLUTION_CASES[@]}"; do - IFS='|' read -r feature label repository prefix last_part_optional suffix_regex request fallback_tag expected <<< "${test_case}" + IFS='|' read -r feature label known_good_variable repository prefix last_part_optional suffix_regex request <<< "${test_case}" + installer="${ROOT_DIR}/src/${feature}/install.sh" + expected="$(sed -n "s/^${known_good_variable}=\"\(.*\)\"$/\1/p" "${installer}")" + if [ -z "${expected}" ]; then + fail "${feature}/${label}: ${known_good_variable} is declared at the top level" + fi + if ! grep 'find_version_from_git_tags' "${installer}" | grep -Fq "\${${known_good_variable}}"; then + fail "${feature}/${label}: resolver uses ${known_good_variable}" + fi + pass "${feature}/${label}: installer wires ${known_good_variable}" + + tag_prefix=${prefix#refs/} + tag_prefix=${tag_prefix#tags/} + fallback_tag="${tag_prefix}${expected}" VERSION="${expected}" rm -f /tmp/version-resolution-network-call git() { @@ -168,7 +181,7 @@ for test_case in "${VERSION_RESOLUTION_CASES[@]}"; do VERSION="${request}" if [ "${feature}" = "go" ]; then go_fallback_versions() { - printf '%s\n' "${FAKE_FALLBACK_TAG}" + printf '%s\n' "${expected}" } find_version_from_git_tags VERSION "${repository}" "${prefix}" . "${last_part_optional}" "${suffix_regex}" "${expected}" "the official Go release index" go_fallback_versions >/tmp/version-resolution-output.log 2>&1 else