Skip to content

Commit c264b4e

Browse files
[python] - issue #1172 - provide switching versions using update-alternatives (#1260)
* [python] - issue #1172 - add version switching mechanism using update-alternatives * removed commented code * [python] - update-alternatives addition for ease in switching of python versions - complete * changes for passing of other tests !! * change for better positioning of code * Added test case scenario for Update-Alternatives check * added scenario for testing * [alternatives] - added option for fedora, centos & rhel * changed file name * changes for update-alternatives test case proper functioning * arrange in asc order for default value retention after testing ends
1 parent 8f42e09 commit c264b4e

File tree

4 files changed

+250
-3
lines changed

4 files changed

+250
-3
lines changed

src/python/install.sh

+25-3
Original file line numberDiff line numberDiff line change
@@ -766,15 +766,19 @@ esac
766766

767767
check_packages ${REQUIRED_PKGS}
768768

769+
# Function to get the major version from a SemVer string
770+
get_major_version() {
771+
local version="$1"
772+
echo "$version" | cut -d '.' -f 1
773+
}
774+
769775
# Install Python from source if needed
770776
if [ "${PYTHON_VERSION}" != "none" ]; then
771777
if ! cat /etc/group | grep -e "^python:" > /dev/null 2>&1; then
772778
groupadd -r python
773779
fi
774780
usermod -a -G python "${USERNAME}"
775-
776781
CURRENT_PATH="${PYTHON_INSTALL_PATH}/current"
777-
778782
install_python ${PYTHON_VERSION}
779783

780784
# Additional python versions to be installed but not be set as default.
@@ -783,9 +787,27 @@ if [ "${PYTHON_VERSION}" != "none" ]; then
783787
OLDIFS=$IFS
784788
IFS=","
785789
read -a additional_versions <<< "$ADDITIONAL_VERSIONS"
786-
for version in "${additional_versions[@]}"; do
790+
major_version=$(get_major_version ${VERSION})
791+
if type apt-get > /dev/null 2>&1; then
792+
# Debian/Ubuntu: Use update-alternatives
793+
update-alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${#additional_versions[@]}+1))
794+
update-alternatives --set python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION}
795+
elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then
796+
# Fedora/RHEL/CentOS: Use alternatives
797+
alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${#additional_versions[@]}+1))
798+
alternatives --set python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION}
799+
fi
800+
for i in "${!additional_versions[@]}"; do
801+
version=${additional_versions[$i]}
787802
OVERRIDE_DEFAULT_VERSION="false"
788803
install_python $version
804+
if type apt-get > /dev/null 2>&1; then
805+
# Debian/Ubuntu: Use update-alternatives
806+
update-alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${i}+1))
807+
elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then
808+
# Fedora/RHEL/CentOS: Use alternatives
809+
alternatives --install ${CURRENT_PATH} python${major_version} ${PYTHON_INSTALL_PATH}/${VERSION} $((${i}+1))
810+
fi
789811
done
790812
INSTALL_PATH="${OLD_INSTALL_PATH}"
791813
IFS=$OLDIFS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library
6+
source dev-container-features-test-lib
7+
8+
check "python version 3.11 installed as default" bash -c "python --version | grep 3.11"
9+
check "python3 version 3.11 installed as default" bash -c "python3 --version | grep 3.11"
10+
check "python version 3.10.5 installed" bash -c "ls -l /usr/local/python | grep 3.10.5"
11+
check "python version 3.8 installed" bash -c "ls -l /usr/local/python | grep 3.8"
12+
check "python version 3.9.13 installed" bash -c "ls -l /usr/local/python | grep 3.9.13"
13+
14+
# Check paths in settings
15+
check "current symlink is correct" bash -c "which python | grep /usr/local/python/current/bin/python"
16+
check "current symlink works" /usr/local/python/current/bin/python --version
17+
18+
# check alternatives command
19+
check_version_switch() {
20+
if type apt-get > /dev/null 2>&1; then
21+
PYTHON_ALTERNATIVES=$(update-alternatives --query python3 | grep -E 'Alternative:|Priority:')
22+
STYLE="debian"
23+
elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then
24+
PYTHON_ALTERNATIVES=$(alternatives --display python3 | grep " - priority")
25+
STYLE="fedora"
26+
else
27+
echo "No supported package manager found."
28+
exit 1
29+
fi
30+
31+
AVAILABLE_VERSIONS=()
32+
INDEX=1
33+
echo "Available Python versions:"
34+
if [ "${STYLE}" = "debian" ]; then
35+
while read -r alt && read -r pri; do
36+
PATH=${alt#Alternative: } # Extract only the path
37+
PRIORITY=${pri#Priority: } # Extract only the priority number
38+
TEMP_VERSIONS+=("${PRIORITY} ${PATH}")
39+
echo "$INDEX) $PATH (Priority: $PRIORITY)"
40+
((INDEX++))
41+
done <<< "${PYTHON_ALTERNATIVES}"
42+
elif [ "${STYLE}" = "fedora" ]; then
43+
export PATH="/usr/bin:$PATH"
44+
# Fedora/RHEL output: one line per alternative in the format:
45+
while IFS= read -r line; do
46+
# Split using " - priority " as a delimiter.
47+
PATH=$(/usr/bin/awk -F' - priority ' '{print $1}' <<< "$line" | /usr/bin/xargs /bin/echo)
48+
PRIORITY=$(/usr/bin/awk -F' - priority ' '{print $2}' <<< "$line" | /usr/bin/xargs /bin/echo)
49+
TEMP_VERSIONS+=("${PRIORITY} ${PATH}")
50+
echo "$INDEX) $PATH (Priority: $PRIORITY_VALUE)"
51+
((INDEX++))
52+
done <<< "${PYTHON_ALTERNATIVES}"
53+
fi
54+
55+
export PATH="/usr/bin:$PATH"
56+
# Sort by priority (numerically ascending)
57+
IFS=$'\n' TEMP_VERSIONS=($(sort -n <<<"${TEMP_VERSIONS[*]}"))
58+
unset IFS
59+
60+
# Populate AVAILABLE_VERSIONS from sorted data
61+
AVAILABLE_VERSIONS=()
62+
INDEX=1
63+
echo -e "\nAvailable Python versions (Sorted in asc order of priority):"
64+
for ENTRY in "${TEMP_VERSIONS[@]}"; do
65+
PRIORITY=${ENTRY%% *} # Extract priority (first part before space)
66+
PATH=${ENTRY#* } # Extract path (everything after first space)
67+
AVAILABLE_VERSIONS+=("${PATH}")
68+
echo "$INDEX) $PATH (Priority: $PRIORITY)"
69+
((INDEX++))
70+
done
71+
72+
echo -e "\nAvailable Versions Count: ${#AVAILABLE_VERSIONS[@]}"
73+
# Ensure at least 4 alternatives exist
74+
if [ "${#AVAILABLE_VERSIONS[@]}" -lt 4 ]; then
75+
echo "Error: Less than 4 Python versions registered in update-alternatives."
76+
exit 1
77+
fi
78+
79+
export PATH="/usr/bin:$PATH"
80+
echo -e "\nSwitching to different versions using update-alternatives --set command...\n"
81+
for CHOICE in {1..4}; do
82+
SELECTED_VERSION="${AVAILABLE_VERSIONS[$((CHOICE - 1))]}"
83+
echo "Switching to: ${SELECTED_VERSION}"
84+
if command -v apt-get > /dev/null 2>&1; then
85+
/usr/bin/update-alternatives --set python3 ${SELECTED_VERSION}
86+
elif command -v dnf > /dev/null 2>&1 || command -v yum > /dev/null 2>&1 || command -v microdnf > /dev/null 2>&1; then
87+
/usr/sbin/alternatives --set python3 ${SELECTED_VERSION}
88+
fi
89+
# Verify the switch
90+
echo "Python version after switch:"
91+
/usr/local/python/current/bin/python3 --version
92+
/bin/sleep 1
93+
echo -e "\n"
94+
done
95+
echo -e "Update-Alternatives --display: \n"
96+
if type apt-get > /dev/null 2>&1; then
97+
/usr/bin/update-alternatives --display python3
98+
elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then
99+
/usr/sbin/alternatives --display python3
100+
fi
101+
}
102+
103+
check "Version Switch With Update_Alternatives" check_version_switch

test/python/scenarios.json

+20
Original file line numberDiff line numberDiff line change
@@ -255,5 +255,25 @@
255255
"enableShared": true
256256
}
257257
}
258+
},
259+
"update_alternatives_switchable_versions": {
260+
"image": "ubuntu:focal",
261+
"features": {
262+
"python": {
263+
"version": "3.11",
264+
"installTools": true,
265+
"additionalVersions": "3.8,3.9.13,3.10.5"
266+
}
267+
}
268+
},
269+
"alternatives_switchable_versions": {
270+
"image": "fedora",
271+
"features": {
272+
"python": {
273+
"version": "3.11",
274+
"installTools": true,
275+
"additionalVersions": "3.8,3.9.13,3.10.5"
276+
}
277+
}
258278
}
259279
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library
6+
source dev-container-features-test-lib
7+
8+
check "python version 3.11 installed as default" bash -c "python --version | grep 3.11"
9+
check "python3 version 3.11 installed as default" bash -c "python3 --version | grep 3.11"
10+
check "python version 3.10.5 installed" bash -c "ls -l /usr/local/python | grep 3.10.5"
11+
check "python version 3.8 installed" bash -c "ls -l /usr/local/python | grep 3.8"
12+
check "python version 3.9.13 installed" bash -c "ls -l /usr/local/python | grep 3.9.13"
13+
14+
# Check paths in settings
15+
check "current symlink is correct" bash -c "which python | grep /usr/local/python/current/bin/python"
16+
check "current symlink works" /usr/local/python/current/bin/python --version
17+
18+
# check alternatives command
19+
check_version_switch() {
20+
if type apt-get > /dev/null 2>&1; then
21+
PYTHON_ALTERNATIVES=$(update-alternatives --query python3 | grep -E 'Alternative:|Priority:')
22+
STYLE="debian"
23+
elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then
24+
PYTHON_ALTERNATIVES=$(alternatives --display python3 | grep " - priority")
25+
STYLE="fedora"
26+
else
27+
echo "No supported package manager found."
28+
exit 1
29+
fi
30+
AVAILABLE_VERSIONS=()
31+
INDEX=1
32+
echo "Available Python versions:"
33+
if [ "${STYLE}" = "debian" ]; then
34+
while read -r alt && read -r pri; do
35+
PATH=${alt#Alternative: } # Extract only the path
36+
PRIORITY=${pri#Priority: } # Extract only the priority number
37+
TEMP_VERSIONS+=("${PRIORITY} ${PATH}")
38+
echo "$INDEX) $PATH (Priority: $PRIORITY)"
39+
((INDEX++))
40+
done <<< "${PYTHON_ALTERNATIVES}"
41+
elif [ "${STYLE}" = "fedora" ]; then
42+
export PATH="/usr/bin:$PATH"
43+
# Fedora/RHEL output: one line per alternative in the format:
44+
while IFS= read -r line; do
45+
# Split using " - priority " as a delimiter.
46+
PATH=$(/usr/bin/awk -F' - priority ' '{print $1}' <<< "$line" | /usr/bin/xargs /bin/echo)
47+
PRIORITY=$(/usr/bin/awk -F' - priority ' '{print $2}' <<< "$line" | /usr/bin/xargs /bin/echo)
48+
TEMP_VERSIONS+=("${PRIORITY} ${PATH}")
49+
echo "$INDEX) $PATH (Priority: $PRIORITY_VALUE)"
50+
((INDEX++))
51+
done <<< "${PYTHON_ALTERNATIVES}"
52+
fi
53+
54+
export PATH="/usr/bin:$PATH"
55+
# Sort by priority (numerically ascending)
56+
IFS=$'\n' TEMP_VERSIONS=($(sort -n <<<"${TEMP_VERSIONS[*]}"))
57+
unset IFS
58+
59+
# Populate AVAILABLE_VERSIONS from sorted data
60+
AVAILABLE_VERSIONS=()
61+
INDEX=1
62+
echo -e "\nAvailable Python versions (Sorted in asc order of priority):"
63+
for ENTRY in "${TEMP_VERSIONS[@]}"; do
64+
PRIORITY=${ENTRY%% *} # Extract priority (first part before space)
65+
PATH=${ENTRY#* } # Extract path (everything after first space)
66+
AVAILABLE_VERSIONS+=("${PATH}")
67+
echo "$INDEX) $PATH (Priority: $PRIORITY)"
68+
((INDEX++))
69+
done
70+
71+
echo -e "\nAvailable Versions Count: ${#AVAILABLE_VERSIONS[@]}\n"
72+
# Ensure at least 4 alternatives exist
73+
if [ "${#AVAILABLE_VERSIONS[@]}" -lt 4 ]; then
74+
echo "Error: Less than 4 Python versions registered in update-alternatives."
75+
exit 1
76+
fi
77+
78+
export PATH="/usr/bin:$PATH"
79+
echo -e "\nSwitching to different versions using update-alternatives --set command...\n"
80+
for CHOICE in {1..4}; do
81+
SELECTED_VERSION="${AVAILABLE_VERSIONS[$((CHOICE - 1))]}"
82+
echo "Switching to: ${SELECTED_VERSION}"
83+
if command -v apt-get > /dev/null 2>&1; then
84+
/usr/bin/update-alternatives --set python3 ${SELECTED_VERSION}
85+
elif command -v dnf > /dev/null 2>&1 || command -v yum > /dev/null 2>&1 || command -v microdnf > /dev/null 2>&1; then
86+
/usr/sbin/alternatives --set python3 ${SELECTED_VERSION}
87+
fi
88+
# Verify the switch
89+
echo "Python version after switch:"
90+
/usr/local/python/current/bin/python3 --version
91+
/bin/sleep 1
92+
echo -e "\n"
93+
done
94+
echo -e "Update-Alternatives --display: \n"
95+
if type apt-get > /dev/null 2>&1; then
96+
/usr/bin/update-alternatives --display python3
97+
elif type dnf > /dev/null 2>&1 || type yum > /dev/null 2>&1 || type microdnf > /dev/null 2>&1; then
98+
/usr/sbin/alternatives --display python3
99+
fi
100+
}
101+
102+
check "Version Switch With Update_Alternatives" check_version_switch

0 commit comments

Comments
 (0)