diff --git a/src/powershell/README.md b/src/powershell/README.md index 31199a4e1..f09ac1f02 100644 --- a/src/powershell/README.md +++ b/src/powershell/README.md @@ -7,7 +7,7 @@ Installs PowerShell along with needed dependencies. Useful for base Dockerfiles ```json "features": { - "ghcr.io/devcontainers/features/powershell:1": {} + "ghcr.io/devcontainers/features/powershell:2": {} } ``` diff --git a/src/powershell/devcontainer-feature.json b/src/powershell/devcontainer-feature.json index f4867cc9e..2f511b212 100644 --- a/src/powershell/devcontainer-feature.json +++ b/src/powershell/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "powershell", - "version": "1.5.1", + "version": "2.0.0", "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.", @@ -9,10 +9,12 @@ "type": "string", "proposals": [ "latest", + "lts", + "preview", + "stable", "none", - "7.4", - "7.3", - "7.2" + "7.5", + "7.4" ], "default": "latest", "description": "Select or enter a version of PowerShell." @@ -20,7 +22,7 @@ "modules": { "type": "string", "default": "", - "description": "Optional comma separated list of PowerShell modules to install. If you need to install a specific version of a module, use '==' to specify the version (e.g. 'az.resources==2.5.0')" + "description": "Optional comma separated list of PowerShell modules to install. If you need to install a specific version of a module, use '==' to specify the version (e.g. 'az.resources==2.5.0')." }, "powershellProfileURL": { "type": "string", diff --git a/src/powershell/install.sh b/src/powershell/install.sh index 3da7a231b..c12b72b75 100755 --- a/src/powershell/install.sh +++ b/src/powershell/install.sh @@ -48,6 +48,26 @@ clean_cache() { rm -rf /var/cache/dnf/* fi } +# Function to resolve PowerShell version from Microsoft redirect URLs +resolve_powershell_version() { + local version_tag="$1" + local redirect_url="https://aka.ms/powershell-release?tag=${version_tag}" + + # Follow the redirect and extract the version from the final URL + local resolved_url + resolved_url=$(curl -sSL -o /dev/null -w '%{url_effective}' "${redirect_url}") + + # Extract version from URL (e.g., https://github.com/PowerShell/PowerShell/releases/tag/v7.4.7 -> 7.4.7) + local resolved_version + resolved_version=$(echo "${resolved_url}" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+(-\w+\.\d+)?' || echo "") + + if [ -z "${resolved_version}" ]; then + echo "Failed to resolve version for tag: ${version_tag}" >&2 + return 1 + fi + + echo "${resolved_version}" +} # Install dependencies for RHEL/CentOS/AlmaLinux (DNF-based systems) install_using_dnf() { dnf remove -y curl-minimal @@ -100,6 +120,58 @@ detect_package_manager() { fi } +# Function to find the latest preview version from git tags +find_preview_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + local repository_url=$2 + + if [ -z "${googlegit_cmd_name}" ]; then + if type git > /dev/null 2>&1; then + git_cmd_name="git" + else + echo "Git not found. Cannot determine preview version." + return 1 + fi + fi + + # Fetch tags from remote repository + local tags + tags=$(git ls-remote --tags "${repository_url}" 2>/dev/null | grep -oP 'refs/tags/v\K[0-9]+\.[0-9]+\.[0-9]+-preview\.[0-9]+' | sort -V) + + if [ -z "${tags}" ]; then + echo "No preview tags found in repository." + return 1 + fi + + local version="" + + if [ "${requested_version}" = "preview" ] || [ "${requested_version}" = "latest" ]; then + # Get the latest preview version + version=$(echo "${tags}" | tail -n 1) + elif [[ "${requested_version}" =~ ^[0-9]+\.[0-9]+$ ]]; then + # Partial version provided (e.g., "7.6"), find latest preview matching that major.minor + version=$(echo "${tags}" | grep "^${requested_version}\." | tail -n 1) + elif [[ "${requested_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-preview$ ]]; then + # Version like "7.6.0-preview" provided, find latest preview for that version + local base_version="${requested_version%-preview}" + version=$(echo "${tags}" | grep "^${base_version}-preview\." | tail -n 1) + elif [[ "${requested_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-preview\.[0-9]+$ ]]; then + # Exact preview version provided, verify it exists + if echo "${tags}" | grep -q "^${requested_version}$"; then + version="${requested_version}" + fi + fi + + if [ -z "${version}" ]; then + echo "Could not find matching preview version for: ${requested_version}" + return 1 + fi + + declare -g "${variable_name}=${version}" + echo "${variable_name}=${version}" +} + # Figure out correct version of a three part version number is not passed find_version_from_git_tags() { local variable_name=$1 @@ -159,7 +231,8 @@ apt_get_update() for package in "$@"; do if ! dnf list installed "$package" > /dev/null 2>&1; then echo "Package $package not installed. Installing using dnf..." - dnf install -y "$package" + # Use --allowerasing to handle conflicts like curl-minimal vs curl + dnf install -y --allowerasing "$package" else echo "Package $package is already installed (DNF)." fi @@ -292,16 +365,30 @@ install_pwsh() { install_using_github() { # Fall back on direct download if no apt package exists in microsoft pool - check_packages curl ca-certificates gnupg2 dirmngr libc6 libgcc1 libgssapi-krb5-2 libstdc++6 libunwind8 libuuid1 zlib1g libicu[0-9][0-9] + if command -v apt-get > /dev/null 2>&1; then + # Debian/Ubuntu dependencies + check_packages curl ca-certificates gnupg2 dirmngr libc6 libgcc1 libgssapi-krb5-2 libstdc++6 libunwind8 libuuid1 zlib1g libicu[0-9][0-9] + elif command -v dnf > /dev/null 2>&1; then + # AlmaLinux/RHEL dependencies + check_packages curl ca-certificates gnupg2 glibc libgcc krb5-libs libstdc++ libuuid zlib libicu wget tar + fi if ! type git > /dev/null 2>&1; then check_packages git fi - - if [ "${architecture}" = "amd64" ]; then + if [ "${architecture}" = "amd64" ] || [ "${architecture}" = "x86_64" ]; then architecture="x64" + elif [ "${architecture}" = "aarch64" ]; then + architecture="arm64" fi pwsh_url="https://github.com/PowerShell/PowerShell" - find_version_from_git_tags POWERSHELL_VERSION $pwsh_url + # Check if we need to find a preview version or stable version + if [[ "${POWERSHELL_VERSION}" == *"preview"* ]] || [ "${POWERSHELL_VERSION}" = "preview" ]; then + echo "Finding preview version..." + find_preview_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" + else + find_version_from_git_tags POWERSHELL_VERSION "${pwsh_url}" + fi + install_pwsh "${POWERSHELL_VERSION}" if grep -q "Not Found" "${powershell_filename}"; then install_prev_pwsh $pwsh_url @@ -312,7 +399,6 @@ install_using_github() { mkdir ~/powershell tar -xvf powershell-${POWERSHELL_VERSION}-linux-x64.tar.gz -C ~/powershell - powershell_archive_sha256="$(cat release.html | tr '\n' ' ' | sed 's|<[^>]*>||g' | grep -oP "${powershell_filename}\s+\K[0-9a-fA-F]{64}" || echo '')" if [ -z "${powershell_archive_sha256}" ]; then echo "(!) WARNING: Failed to retrieve SHA256 for archive. Skipping validaiton." @@ -323,13 +409,33 @@ install_using_github() { tar xf "${powershell_filename}" -C "${powershell_target_path}" chmod 755 "${powershell_target_path}/pwsh" ln -sf "${powershell_target_path}/pwsh" /usr/bin/pwsh - add-shell "/usr/bin/pwsh" + # Add pwsh to /etc/shells + if command -v add-shell > /dev/null 2>&1; then + # Debian/Ubuntu - use add-shell + add-shell "/usr/bin/pwsh" + else + # AlmaLinux/RHEL - manually add to /etc/shells - add-shell is not available in almalinux repos and manual approach is simpler than adding a dependency just for this + if ! grep -q "/usr/bin/pwsh" /etc/shells; then + echo "/usr/bin/pwsh" >> /etc/shells + fi + fi cd /tmp rm -rf /tmp/pwsh } if ! type pwsh >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive + if [ "${POWERSHELL_VERSION}" = "lts" ] || [ "${POWERSHELL_VERSION}" = "stable" ] || [ "${POWERSHELL_VERSION}" = "preview" ]; then + echo "Resolving PowerShell '${POWERSHELL_VERSION}' version from Microsoft..." + resolved_version=$(resolve_powershell_version "${POWERSHELL_VERSION}") + if [ -n "${resolved_version}" ]; then + echo "Resolved '${POWERSHELL_VERSION}' to version: ${resolved_version}" + POWERSHELL_VERSION="${resolved_version}" + else + echo "Warning: Could not resolve '${POWERSHELL_VERSION}' version. Falling back to 'latest'." + POWERSHELL_VERSION="latest" + fi + fi # Source /etc/os-release to get OS info . /etc/os-release @@ -340,11 +446,10 @@ if ! type pwsh >/dev/null 2>&1; then POWERSHELL_ARCHIVE_ARCHITECTURES="${POWERSHELL_ARCHIVE_ARCHITECTURES_ALMALINUX}" fi - if [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_UBUNTU}"* ]] && [[ "${POWERSHELL_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then + if [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_UBUNTU}"* ]] && [[ "${POWERSHELL_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]] && [[ "${POWERSHELL_VERSION}" != *"preview"* ]]; then install_using_apt || use_github="true" - elif [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_ALMALINUX}"* ]]; then + elif [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_ALMALINUX}"* ]] && [[ "${POWERSHELL_VERSION}" != *"preview"* ]]; then install_using_dnf && install_powershell_dnf || use_github="true" - else use_github="true" fi diff --git a/test/powershell/install_modules.sh b/test/powershell/install_modules.sh index 1415af2c1..27872a479 100644 --- a/test/powershell/install_modules.sh +++ b/test/powershell/install_modules.sh @@ -5,6 +5,9 @@ set -e # Import test library for `check` command source dev-container-features-test-lib +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is LTS (not preview)" bash -c "pwsh --version | grep -v 'preview'" + # Extension-specific tests check "az.resources" pwsh -Command "(Get-Module -ListAvailable -Name Az.Resources).Version.ToString()" check "az.storage" pwsh -Command "(Get-Module -ListAvailable -Name Az.Storage).Version.ToString()" diff --git a/test/powershell/install_powershell_fallback_test.sh b/test/powershell/install_powershell_fallback_test.sh index 169863c7b..b12e39b6e 100644 --- a/test/powershell/install_powershell_fallback_test.sh +++ b/test/powershell/install_powershell_fallback_test.sh @@ -148,8 +148,10 @@ install_pwsh() { install_using_github() { mode=$1 - if [ "${architecture}" = "amd64" ]; then + if [ "${architecture}" = "amd64" ] || [ "${architecture}" = "x86_64" ]; then architecture="x64" + elif [ "${architecture}" = "aarch64" ]; then + architecture="arm64" fi pwsh_url="https://github.com/PowerShell/PowerShell" POWERSHELL_VERSION="7.4.xyz" diff --git a/test/powershell/powershell_lts_version_almalinux.sh b/test/powershell/powershell_lts_version_almalinux.sh new file mode 100755 index 000000000..a37598913 --- /dev/null +++ b/test/powershell/powershell_lts_version_almalinux.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test LTS version installation on AlmaLinux +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is LTS (not preview)" bash -c "pwsh --version | grep -v 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_lts_version_debian.sh b/test/powershell/powershell_lts_version_debian.sh new file mode 100755 index 000000000..c216c478b --- /dev/null +++ b/test/powershell/powershell_lts_version_debian.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test LTS version installation on Debian +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is LTS (not preview)" bash -c "pwsh --version | grep -v 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_preview_version.sh b/test/powershell/powershell_preview_version.sh new file mode 100755 index 000000000..7e49ded9e --- /dev/null +++ b/test/powershell/powershell_preview_version.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test preview version installation +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is preview" bash -c "pwsh --version | grep -i 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_preview_version_almalinux.sh b/test/powershell/powershell_preview_version_almalinux.sh new file mode 100755 index 000000000..3a8128209 --- /dev/null +++ b/test/powershell/powershell_preview_version_almalinux.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test preview version installation on AlmaLinux +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is preview" bash -c "pwsh --version | grep -i 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_preview_version_debian.sh b/test/powershell/powershell_preview_version_debian.sh new file mode 100755 index 000000000..316017cf7 --- /dev/null +++ b/test/powershell/powershell_preview_version_debian.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test preview version installation on Debian +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is preview" bash -c "pwsh --version | grep -i 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_stable_version.sh b/test/powershell/powershell_stable_version.sh new file mode 100755 index 000000000..b82439ff8 --- /dev/null +++ b/test/powershell/powershell_stable_version.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test stable version installation +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is stable (not preview)" bash -c "pwsh --version | grep -v 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_stable_version_almalinux.sh b/test/powershell/powershell_stable_version_almalinux.sh new file mode 100755 index 000000000..7695995f5 --- /dev/null +++ b/test/powershell/powershell_stable_version_almalinux.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test stable version installation on AlmaLinux +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is stable (not preview)" bash -c "pwsh --version | grep -v 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/powershell_stable_version_debian.sh b/test/powershell/powershell_stable_version_debian.sh new file mode 100755 index 000000000..45a14c90f --- /dev/null +++ b/test/powershell/powershell_stable_version_debian.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Test stable version installation on Debian +check "pwsh is installed" bash -c "command -v pwsh" +check "pwsh version is stable (not preview)" bash -c "pwsh --version | grep -v 'preview'" +check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" + +# Report result +reportResults diff --git a/test/powershell/scenarios.json b/test/powershell/scenarios.json index 781ebaf86..3e9b3acd3 100644 --- a/test/powershell/scenarios.json +++ b/test/powershell/scenarios.json @@ -3,6 +3,7 @@ "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { "powershell": { + "version": "lts", "modules": "az.resources, az.storage", "powershellProfileURL": "https://raw.githubusercontent.com/codspace/powershell-profile/main/Test-Profile.ps1" } @@ -31,10 +32,88 @@ "powershell": {} } }, + "validate_powershell_installation_spec": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "powershell": { + "version": "7.5" + } + } + }, + "validate_powershell_installation_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "powershell": {} + } + }, "powershell_alma_linux": { "image": "almalinux:9", "features": { "powershell": {} } + }, + "powershell_stable_version": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "powershell": { + "version": "stable" + } + } + }, + "powershell_preview_version": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "powershell": { + "version": "preview" + } + } + }, + "powershell_stable_version_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "powershell": { + "version": "stable" + } + } + }, + "powershell_lts_version_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "powershell": { + "version": "lts" + } + } + }, + "powershell_preview_version_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "powershell": { + "version": "preview" + } + } + }, + "powershell_lts_version_almalinux": { + "image": "almalinux:9", + "features": { + "powershell": { + "version": "lts" + } + } + }, + "powershell_stable_version_almalinux": { + "image": "almalinux:9", + "features": { + "powershell": { + "version": "stable" + } + } + }, + "powershell_preview_version_almalinux": { + "image": "almalinux:9", + "features": { + "powershell": { + "version": "preview" + } + } } } diff --git a/test/powershell/validate_powershell_installation_debian.sh b/test/powershell/validate_powershell_installation_debian.sh new file mode 100644 index 000000000..20b930d26 --- /dev/null +++ b/test/powershell/validate_powershell_installation_debian.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Extension-specific tests +check "pwsh file is symlink" bash -c "[ -L /usr/bin/pwsh ]" +check "pwsh symlink is registered as shell" bash -c "[ $(grep -c '/usr/bin/pwsh' /etc/shells) -eq 1 ]" +check "pwsh target is correct" bash -c "[ $(readlink /usr/bin/pwsh) = /opt/microsoft/powershell/7/pwsh ]" +check "pwsh target is registered as shell" bash -c "[ $(grep -c '/opt/microsoft/powershell/7/pwsh' /etc/shells) -eq 1 ]" +check "pwsh owner is root" bash -c "[ $(stat -c %U /opt/microsoft/powershell/7/pwsh) = root ]" +check "pwsh group is root" bash -c "[ $(stat -c %G /opt/microsoft/powershell/7/pwsh) = root ]" +check "pwsh file mode is -rwxr-xr-x" bash -c "[ $(stat -c '%A' /opt/microsoft/powershell/7/pwsh) = '-rwxr-xr-x' ]" +check "pwsh is in PATH" bash -c "command -v pwsh" + +# Report result +reportResults + diff --git a/test/powershell/validate_powershell_installation_spec.sh b/test/powershell/validate_powershell_installation_spec.sh new file mode 100644 index 000000000..20b930d26 --- /dev/null +++ b/test/powershell/validate_powershell_installation_spec.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Extension-specific tests +check "pwsh file is symlink" bash -c "[ -L /usr/bin/pwsh ]" +check "pwsh symlink is registered as shell" bash -c "[ $(grep -c '/usr/bin/pwsh' /etc/shells) -eq 1 ]" +check "pwsh target is correct" bash -c "[ $(readlink /usr/bin/pwsh) = /opt/microsoft/powershell/7/pwsh ]" +check "pwsh target is registered as shell" bash -c "[ $(grep -c '/opt/microsoft/powershell/7/pwsh' /etc/shells) -eq 1 ]" +check "pwsh owner is root" bash -c "[ $(stat -c %U /opt/microsoft/powershell/7/pwsh) = root ]" +check "pwsh group is root" bash -c "[ $(stat -c %G /opt/microsoft/powershell/7/pwsh) = root ]" +check "pwsh file mode is -rwxr-xr-x" bash -c "[ $(stat -c '%A' /opt/microsoft/powershell/7/pwsh) = '-rwxr-xr-x' ]" +check "pwsh is in PATH" bash -c "command -v pwsh" + +# Report result +reportResults +