diff --git a/src/python/install.sh b/src/python/install.sh index b3311c7c6..fec8937a0 100755 --- a/src/python/install.sh +++ b/src/python/install.sh @@ -766,15 +766,19 @@ esac check_packages ${REQUIRED_PKGS} +# Function to get the major version from a SemVer string +get_major_version() { + local version="$1" + echo "$version" | cut -d '.' -f 1 +} + # Install Python from source if needed if [ "${PYTHON_VERSION}" != "none" ]; then if ! cat /etc/group | grep -e "^python:" > /dev/null 2>&1; then groupadd -r python fi usermod -a -G python "${USERNAME}" - CURRENT_PATH="${PYTHON_INSTALL_PATH}/current" - install_python ${PYTHON_VERSION} # Additional python versions to be installed but not be set as default. @@ -783,9 +787,27 @@ if [ "${PYTHON_VERSION}" != "none" ]; then OLDIFS=$IFS IFS="," read -a additional_versions <<< "$ADDITIONAL_VERSIONS" - for version in "${additional_versions[@]}"; do + major_version=$(get_major_version ${VERSION}) + if type apt-get > /dev/null 2>&1; then + # Debian/Ubuntu: Use update-alternatives + update-alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${#additional_versions[@]}+1)) + update-alternatives --set python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} + elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then + # Fedora/RHEL/CentOS: Use alternatives + alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${#additional_versions[@]}+1)) + alternatives --set python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} + fi + for i in "${!additional_versions[@]}"; do + version=${additional_versions[$i]} OVERRIDE_DEFAULT_VERSION="false" install_python $version + if type apt-get > /dev/null 2>&1; then + # Debian/Ubuntu: Use update-alternatives + update-alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${i}+1)) + elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then + # Fedora/RHEL/CentOS: Use alternatives + alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${i}+1)) + fi done INSTALL_PATH="${OLD_INSTALL_PATH}" IFS=$OLDIFS diff --git a/test/python/alternatives_switchable_versions.sh b/test/python/alternatives_switchable_versions.sh new file mode 100644 index 000000000..a29392c17 --- /dev/null +++ b/test/python/alternatives_switchable_versions.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +check "python version 3.11 installed as default" bash -c "python --version | grep 3.11" +check "python3 version 3.11 installed as default" bash -c "python3 --version | grep 3.11" +check "python version 3.10.5 installed" bash -c "ls -l /usr/local/python | grep 3.10.5" +check "python version 3.8 installed" bash -c "ls -l /usr/local/python | grep 3.8" +check "python version 3.9.13 installed" bash -c "ls -l /usr/local/python | grep 3.9.13" + +# Check paths in settings +check "current symlink is correct" bash -c "which python | grep /usr/local/python/current/bin/python" +check "current symlink works" /usr/local/python/current/bin/python --version + +# check alternatives command +check_version_switch() { + if type apt-get > /dev/null 2>&1; then + PYTHON_ALTERNATIVES=$(update-alternatives --query python3 | grep -E 'Alternative:|Priority:') + STYLE="debian" + elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then + PYTHON_ALTERNATIVES=$(alternatives --display python3 | grep " - priority") + STYLE="fedora" + else + echo "No supported package manager found." + exit 1 + fi + + AVAILABLE_VERSIONS=() + INDEX=1 + echo "Available Python versions:" + if [ "${STYLE}" = "debian" ]; then + while read -r alt && read -r pri; do + PATH=${alt#Alternative: } # Extract only the path + PRIORITY=${pri#Priority: } # Extract only the priority number + TEMP_VERSIONS+=("${PRIORITY} ${PATH}") + echo "$INDEX) $PATH (Priority: $PRIORITY)" + ((INDEX++)) + done <<< "${PYTHON_ALTERNATIVES}" + elif [ "${STYLE}" = "fedora" ]; then + export PATH="/usr/bin:$PATH" + # Fedora/RHEL output: one line per alternative in the format: + while IFS= read -r line; do + # Split using " - priority " as a delimiter. + PATH=$(/usr/bin/awk -F' - priority ' '{print $1}' <<< "$line" | /usr/bin/xargs /bin/echo) + PRIORITY=$(/usr/bin/awk -F' - priority ' '{print $2}' <<< "$line" | /usr/bin/xargs /bin/echo) + TEMP_VERSIONS+=("${PRIORITY} ${PATH}") + echo "$INDEX) $PATH (Priority: $PRIORITY_VALUE)" + ((INDEX++)) + done <<< "${PYTHON_ALTERNATIVES}" + fi + + export PATH="/usr/bin:$PATH" + # Sort by priority (numerically ascending) + IFS=$'\n' TEMP_VERSIONS=($(sort -n <<<"${TEMP_VERSIONS[*]}")) + unset IFS + + # Populate AVAILABLE_VERSIONS from sorted data + AVAILABLE_VERSIONS=() + INDEX=1 + echo -e "\nAvailable Python versions (Sorted in asc order of priority):" + for ENTRY in "${TEMP_VERSIONS[@]}"; do + PRIORITY=${ENTRY%% *} # Extract priority (first part before space) + PATH=${ENTRY#* } # Extract path (everything after first space) + AVAILABLE_VERSIONS+=("${PATH}") + echo "$INDEX) $PATH (Priority: $PRIORITY)" + ((INDEX++)) + done + + echo -e "\nAvailable Versions Count: ${#AVAILABLE_VERSIONS[@]}" + # Ensure at least 4 alternatives exist + if [ "${#AVAILABLE_VERSIONS[@]}" -lt 4 ]; then + echo "Error: Less than 4 Python versions registered in update-alternatives." + exit 1 + fi + + export PATH="/usr/bin:$PATH" + echo -e "\nSwitching to different versions using update-alternatives --set command...\n" + for CHOICE in {1..4}; do + SELECTED_VERSION="${AVAILABLE_VERSIONS[$((CHOICE - 1))]}" + echo "Switching to: ${SELECTED_VERSION}" + if command -v apt-get > /dev/null 2>&1; then + /usr/bin/update-alternatives --set python3 ${SELECTED_VERSION} + elif command -v dnf > /dev/null 2>&1 || command -v yum > /dev/null 2>&1 || command -v microdnf > /dev/null 2>&1; then + /usr/sbin/alternatives --set python3 ${SELECTED_VERSION} + fi + # Verify the switch + echo "Python version after switch:" + /usr/local/python/current/bin/python3 --version + /bin/sleep 1 + echo -e "\n" + done + echo -e "Update-Alternatives --display: \n" + if type apt-get > /dev/null 2>&1; then + /usr/bin/update-alternatives --display python3 + elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then + /usr/sbin/alternatives --display python3 + fi +} + +check "Version Switch With Update_Alternatives" check_version_switch \ No newline at end of file diff --git a/test/python/scenarios.json b/test/python/scenarios.json index be37869df..9dc37e16c 100644 --- a/test/python/scenarios.json +++ b/test/python/scenarios.json @@ -255,5 +255,25 @@ "enableShared": true } } + }, + "update_alternatives_switchable_versions": { + "image": "ubuntu:focal", + "features": { + "python": { + "version": "3.11", + "installTools": true, + "additionalVersions": "3.8,3.9.13,3.10.5" + } + } + }, + "alternatives_switchable_versions": { + "image": "fedora", + "features": { + "python": { + "version": "3.11", + "installTools": true, + "additionalVersions": "3.8,3.9.13,3.10.5" + } + } } } \ No newline at end of file diff --git a/test/python/update_alternatives_switchable_versions.sh b/test/python/update_alternatives_switchable_versions.sh new file mode 100644 index 000000000..07a517332 --- /dev/null +++ b/test/python/update_alternatives_switchable_versions.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +check "python version 3.11 installed as default" bash -c "python --version | grep 3.11" +check "python3 version 3.11 installed as default" bash -c "python3 --version | grep 3.11" +check "python version 3.10.5 installed" bash -c "ls -l /usr/local/python | grep 3.10.5" +check "python version 3.8 installed" bash -c "ls -l /usr/local/python | grep 3.8" +check "python version 3.9.13 installed" bash -c "ls -l /usr/local/python | grep 3.9.13" + +# Check paths in settings +check "current symlink is correct" bash -c "which python | grep /usr/local/python/current/bin/python" +check "current symlink works" /usr/local/python/current/bin/python --version + +# check alternatives command +check_version_switch() { + if type apt-get > /dev/null 2>&1; then + PYTHON_ALTERNATIVES=$(update-alternatives --query python3 | grep -E 'Alternative:|Priority:') + STYLE="debian" + elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then + PYTHON_ALTERNATIVES=$(alternatives --display python3 | grep " - priority") + STYLE="fedora" + else + echo "No supported package manager found." + exit 1 + fi + AVAILABLE_VERSIONS=() + INDEX=1 + echo "Available Python versions:" + if [ "${STYLE}" = "debian" ]; then + while read -r alt && read -r pri; do + PATH=${alt#Alternative: } # Extract only the path + PRIORITY=${pri#Priority: } # Extract only the priority number + TEMP_VERSIONS+=("${PRIORITY} ${PATH}") + echo "$INDEX) $PATH (Priority: $PRIORITY)" + ((INDEX++)) + done <<< "${PYTHON_ALTERNATIVES}" + elif [ "${STYLE}" = "fedora" ]; then + export PATH="/usr/bin:$PATH" + # Fedora/RHEL output: one line per alternative in the format: + while IFS= read -r line; do + # Split using " - priority " as a delimiter. + PATH=$(/usr/bin/awk -F' - priority ' '{print $1}' <<< "$line" | /usr/bin/xargs /bin/echo) + PRIORITY=$(/usr/bin/awk -F' - priority ' '{print $2}' <<< "$line" | /usr/bin/xargs /bin/echo) + TEMP_VERSIONS+=("${PRIORITY} ${PATH}") + echo "$INDEX) $PATH (Priority: $PRIORITY_VALUE)" + ((INDEX++)) + done <<< "${PYTHON_ALTERNATIVES}" + fi + + export PATH="/usr/bin:$PATH" + # Sort by priority (numerically ascending) + IFS=$'\n' TEMP_VERSIONS=($(sort -n <<<"${TEMP_VERSIONS[*]}")) + unset IFS + + # Populate AVAILABLE_VERSIONS from sorted data + AVAILABLE_VERSIONS=() + INDEX=1 + echo -e "\nAvailable Python versions (Sorted in asc order of priority):" + for ENTRY in "${TEMP_VERSIONS[@]}"; do + PRIORITY=${ENTRY%% *} # Extract priority (first part before space) + PATH=${ENTRY#* } # Extract path (everything after first space) + AVAILABLE_VERSIONS+=("${PATH}") + echo "$INDEX) $PATH (Priority: $PRIORITY)" + ((INDEX++)) + done + + echo -e "\nAvailable Versions Count: ${#AVAILABLE_VERSIONS[@]}\n" + # Ensure at least 4 alternatives exist + if [ "${#AVAILABLE_VERSIONS[@]}" -lt 4 ]; then + echo "Error: Less than 4 Python versions registered in update-alternatives." + exit 1 + fi + + export PATH="/usr/bin:$PATH" + echo -e "\nSwitching to different versions using update-alternatives --set command...\n" + for CHOICE in {1..4}; do + SELECTED_VERSION="${AVAILABLE_VERSIONS[$((CHOICE - 1))]}" + echo "Switching to: ${SELECTED_VERSION}" + if command -v apt-get > /dev/null 2>&1; then + /usr/bin/update-alternatives --set python3 ${SELECTED_VERSION} + elif command -v dnf > /dev/null 2>&1 || command -v yum > /dev/null 2>&1 || command -v microdnf > /dev/null 2>&1; then + /usr/sbin/alternatives --set python3 ${SELECTED_VERSION} + fi + # Verify the switch + echo "Python version after switch:" + /usr/local/python/current/bin/python3 --version + /bin/sleep 1 + echo -e "\n" + done + echo -e "Update-Alternatives --display: \n" + if type apt-get > /dev/null 2>&1; then + /usr/bin/update-alternatives --display python3 + elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then + /usr/sbin/alternatives --display python3 + fi +} + +check "Version Switch With Update_Alternatives" check_version_switch \ No newline at end of file