From 6e508935b0251b24d971ced4154f959599561ade Mon Sep 17 00:00:00 2001 From: Mathiyarasy <157102811+Mathiyarasy@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:44:45 +0530 Subject: [PATCH 01/78] Update Python Feature Installation: Support Sigstore Signatures for Python 3.14+ and Fix OpenSSL Build on CentOS 7/RHEL 7 (#1508) * cogin verfiy for python * Fix CentOS 7 Python installation by adding missing perl-Time-Piece dependency * update feature version * fix issues --- src/python/devcontainer-feature.json | 2 +- src/python/install.sh | 175 +++++++++++++++++++++++++-- 2 files changed, 168 insertions(+), 9 deletions(-) diff --git a/src/python/devcontainer-feature.json b/src/python/devcontainer-feature.json index 635b233cf..6a4121415 100644 --- a/src/python/devcontainer-feature.json +++ b/src/python/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "python", - "version": "1.7.1", + "version": "1.8.0", "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 fec8937a0..be895927d 100755 --- a/src/python/install.sh +++ b/src/python/install.sh @@ -69,7 +69,7 @@ if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${VERSION_CODENAME-}" = "centos7" ]; then fi # To find some devel packages, some rhel need to enable specific extra repos, but not on RedHat ubi images... -INSTALL_CMD_ADDL_REPO="" +INSTALL_CMD_ADDL_REPOS="" if [ ${ADJUSTED_ID} = "rhel" ] && [ ${ID} != "rhel" ]; then if [ ${MAJOR_VERSION_ID} = "8" ]; then INSTALL_CMD_ADDL_REPOS="--enablerepo powertools" @@ -93,6 +93,27 @@ else INSTALL_CMD="${PKG_MGR_CMD} ${INSTALL_CMD_ADDL_REPOS} -y install --noplugins --setopt=install_weak_deps=0" fi +# Install Time::Piece Perl module required by OpenSSL 3.0.18+ build system on CentOS 7/RHEL 7 +install_time_piece() { + echo "(*) Ensuring Time::Piece Perl module is available for OpenSSL 3.0.18+ build..." + + # Check if Time::Piece is already available (it's usually in Perl core) + if perl -MTime::Piece -e 'exit 0' 2>/dev/null; then + echo "(*) Time::Piece already available" + return 0 + fi + + echo "(*) Time::Piece not found, installing perl-Time-Piece package..." + + # Install perl-Time-Piece package for CentOS 7/RHEL 7 + if ${INSTALL_CMD} perl-Time-Piece; then + echo "(*) perl-Time-Piece installed for OpenSSL 3.0.18+ build" + else + echo "(!) Failed to install perl-Time-Piece package. This will cause OpenSSL 3.0.18+ build to fail" + return 1 + fi +} + # Clean up clean_up() { case ${ADJUSTED_ID} in @@ -478,6 +499,130 @@ install_cpython() { curl -sSL -o "/tmp/python-src/${cpython_tgz_filename}" "${cpython_tgz_url}" fi } +# Get system architecture for downloads +get_architecture() { + local architecture="" + case $(uname -m) in + x86_64) architecture="amd64" ;; + aarch64 | armv8*) architecture="arm64" ;; + aarch32 | armv7* | armvhf*) architecture="armhf" ;; + i?86) architecture="386" ;; + *) echo "(!) Architecture $(uname -m) unsupported"; exit 1 ;; + esac + echo ${architecture} +} + +# Install cosign with multi-distro support +install_cosign() { + + COSIGN_VERSION="latest" + local cosign_url='https://github.com/sigstore/cosign' + local architecture=$(get_architecture) + + find_version_from_git_tags COSIGN_VERSION "${cosign_url}" + + # Remove 'v' prefix if present for download URL + local version_for_url="${COSIGN_VERSION#v}" + + local cosign_filename="/tmp/cosign_${version_for_url}_${architecture}.deb" + local cosign_url="https://github.com/sigstore/cosign/releases/download/v${version_for_url}/cosign_${version_for_url}_${architecture}.deb" + + echo "Downloading cosign from: ${cosign_url}" + + if curl -L -f --fail-with-body "${cosign_url}" -o "$cosign_filename" 2>/dev/null; then + echo "(*) Successfully downloaded cosign v${COSIGN_VERSION}" + else + echo -e "\n(!) Failed to fetch cosign v${COSIGN_VERSION}..." + # Try previous version + find_prev_version_from_git_tags COSIGN_VERSION "https://github.com/sigstore/cosign" + echo -e "\nAttempting to install previous cosign ${COSIGN_VERSION} version as fallback mechanism" + + version_for_url="${COSIGN_VERSION#v}" + cosign_filename="/tmp/cosign_${version_for_url}_${architecture}.deb" + cosign_url="https://github.com/sigstore/cosign/releases/download/v${version_for_url}/cosign_${version_for_url}_${architecture}.deb" + + if ! curl -L -f --fail-with-body "${cosign_url}" -o "$cosign_filename" 2>/dev/null; then + echo "(!) Failed to download cosign v${COSIGN_VERSION} as fallback" + return 1 + fi + fi + + # Install the package + if [ -f "$cosign_filename" ]; then + dpkg -i "$cosign_filename" + rm "$cosign_filename" + echo "Installation of cosign succeeded with ${COSIGN_VERSION}." + else + echo "(!) Failed to install cosign package" + return 1 + fi + +} + +# COSIGN signature verification for python versions >= 3.14 +cosign_verification() { + local VERSION="$1" + + # Ensure cosign is installed + if ! type cosign > /dev/null 2>&1; then + echo "(*) cosign not found, installing..." + if ! install_cosign; then + echo "(!) Failed to install cosign" + return 1 + fi + else + echo "(*) cosign is already available on the system" + fi + + echo "(*) Attempting COSIGN verification for Python ${VERSION}..." + + # Check if COSIGN signature files exist (these don't exist yet for Python releases) + local cosign_sig_url="${cpython_tgz_url}.sig" + local cosign_cert_url="${cpython_tgz_url}.pem" + + # Download COSIGN signature and certificate files with proper error handling + echo "(*) Checking for cosign signature files..." + if ! curl -sSL -f --fail-with-body -o "/tmp/python-src/${cpython_tgz_filename}.sig" "${cosign_sig_url}" 2>/dev/null; then + echo "(!) COSIGN signature file not available for Python ${VERSION}" + echo " Signature URL: ${cosign_sig_url}" + return 1 + fi + + if ! curl -sSL -f --fail-with-body -o "/tmp/python-src/${cpython_tgz_filename}.pem" "${cosign_cert_url}" 2>/dev/null; then + echo "(!) COSIGN certificate file not available for Python ${VERSION}" + echo " Certificate URL: ${cosign_cert_url}" + return 1 + fi + + # Perform COSIGN verification + if cosign verify-blob \ + --certificate "/tmp/python-src/${cpython_tgz_filename}.pem" \ + --signature "/tmp/python-src/${cpython_tgz_filename}.sig" \ + --certificate-identity-regexp=".*" \ + --certificate-oidc-issuer-regexp=".*" \ + "/tmp/python-src/${cpython_tgz_filename}"; then + echo "(*) COSIGN signature verification successful" + return 0 + else + echo "(!) COSIGN signature verification failed" + return 1 + fi +} + +# GPG verification for python versions < 3.14 +gpg_verification() { + echo "(*) Using GPG signature verification..." + if [[ ${VERSION_CODENAME} = "centos7" ]] || [[ ${VERSION_CODENAME} = "rhel7" ]]; then + receive_gpg_keys_centos7 PYTHON_SOURCE_GPG_KEYS + else + receive_gpg_keys PYTHON_SOURCE_GPG_KEYS + fi + echo "Downloading ${cpython_tgz_filename}.asc..." + curl -sSL -o "/tmp/python-src/${cpython_tgz_filename}.asc" "${cpython_tgz_url}.asc" + gpg --verify "${cpython_tgz_filename}.asc" + echo "(*) GPG signature verification successful" +} + install_from_source() { VERSION=$1 @@ -496,6 +641,8 @@ install_from_source() { case ${VERSION_CODENAME} in centos7|rhel7) check_packages perl-IPC-Cmd + # Install Time::Piece Perl module required by OpenSSL 3.0.18+ build system + install_time_piece install_openssl3 ADDL_CONFIG_ARGS="--with-openssl=${SSL_INSTALL_PATH} --with-openssl-rpath=${SSL_INSTALL_PATH}/lib" ;; @@ -507,15 +654,27 @@ install_from_source() { install_prev_vers_cpython "${VERSION}" fi fi; - # Verify signature - if [[ ${VERSION_CODENAME} = "centos7" ]] || [[ ${VERSION_CODENAME} = "rhel7" ]]; then - receive_gpg_keys_centos7 PYTHON_SOURCE_GPG_KEYS + + # Discontinuation of PGP signatures for releases of Python 3.14 or future versions + # CPython release artifacts are additionally signed with Sigstore starting with the Python 3.11.0 + local major_version=$(echo "$VERSION" | cut -d. -f1) + local minor_version=$(echo "$VERSION" | cut -d. -f2) + echo "(*) Detected Python version: ${major_version}.${minor_version}" + if (( major_version > 3 )) || { (( major_version == 3 )) && (( minor_version >= 14 )); }; then + echo "(*) Python 3.14+ detected. Attempting cosign verification..." + if cosign_verification "$VERSION"; then + echo "(*) COSIGN verification successful." + else + echo "(*) COSIGN verification failed or not available for Python ${VERSION}" + echo "(*) WARNING: Installing Python ${VERSION} without signature verification" + echo "(*) This is expected for newly released versions where cosign signatures are not yet available" + echo "(*) Python 3.14+ discontinued PGP signatures in favor of cosign, but cosign signatures may take time to be published" + fi else - receive_gpg_keys PYTHON_SOURCE_GPG_KEYS + echo "(*) Python < 3.14 detected. Using GPG signature verification..." + gpg_verification + echo "(*) GPG verification successful." fi - echo "Downloading ${cpython_tgz_filename}.asc..." - curl -sSL -o "/tmp/python-src/${cpython_tgz_filename}.asc" "${cpython_tgz_url}.asc" - gpg --verify "${cpython_tgz_filename}.asc" # Update min protocol for testing only - https://bugs.python.org/issue41561 if [ -f /etc/pki/tls/openssl.cnf ]; then From 10b6e7150db6b9d5adb76fb80b5cd4455ec03e82 Mon Sep 17 00:00:00 2001 From: Mathiyarasy <157102811+Mathiyarasy@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:46:08 +0530 Subject: [PATCH 02/78] Fix Azure-cli installation for trixie (#1489) * For trixie fix azure-cli installation * rename file * missing shabang line * update version * add comments --- src/azure-cli/devcontainer-feature.json | 2 +- src/azure-cli/install.sh | 12 +++++++++-- ...tall_azcli_dotnet_dockerindocker_trixie.sh | 13 ++++++++++++ test/azure-cli/scenarios.json | 21 ++++++++++++++++--- 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 test/azure-cli/install_azcli_dotnet_dockerindocker_trixie.sh diff --git a/src/azure-cli/devcontainer-feature.json b/src/azure-cli/devcontainer-feature.json index 3bc905f47..a634818b6 100644 --- a/src/azure-cli/devcontainer-feature.json +++ b/src/azure-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "azure-cli", - "version": "1.2.8", + "version": "1.2.9", "name": "Azure CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/azure-cli", "description": "Installs the Azure CLI along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.", diff --git a/src/azure-cli/install.sh b/src/azure-cli/install.sh index 171c24510..bc5744ce2 100755 --- a/src/azure-cli/install.sh +++ b/src/azure-cli/install.sh @@ -98,7 +98,15 @@ install_using_apt() { # Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/azure-cli/ ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/azure-cli.list - apt-get update + + # This is a workaround for the fact that Azure CLI is not yet a supported package for debian trixie. Once Azure CLI is supported we should revert to avoid script failure for non Azure CLI packages + if ! (apt-get update); then + echo "(!) Failed to update apt cache, removing repository file" + rm -f /etc/apt/sources.list.d/azure-cli.list + else + echo "WARNING: apt-get update succeeded. The workaround for broken Azure CLI install on Debian Trixie may no longer be needed." + echo "TODO: Consider reverting to a simple `apt-get update` if the Azure CLI repo works reliably for debian trixie" + fi if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then # Empty, meaning grab the "latest" in the apt repo @@ -135,7 +143,7 @@ install_using_pip_strategy() { install_with_pipx() { echo "(*) Attempting to install globally with pipx..." - local ver="$1" + local ver="$1" export local diff --git a/test/azure-cli/install_azcli_dotnet_dockerindocker_trixie.sh b/test/azure-cli/install_azcli_dotnet_dockerindocker_trixie.sh new file mode 100644 index 000000000..3f5e6f540 --- /dev/null +++ b/test/azure-cli/install_azcli_dotnet_dockerindocker_trixie.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +check "version" az --version + +check "docker installed" bash -c "type docker" + +# Report result +reportResults \ No newline at end of file diff --git a/test/azure-cli/scenarios.json b/test/azure-cli/scenarios.json index c3a205e68..4c12034b5 100644 --- a/test/azure-cli/scenarios.json +++ b/test/azure-cli/scenarios.json @@ -8,7 +8,7 @@ "extensions": "aks-preview,amg,containerapp" } } - }, + }, "install_extensions": { "image": "mcr.microsoft.com/devcontainers/base:jammy", "user": "vscode", @@ -39,7 +39,7 @@ } } }, - "install_bicep_trixie": { + "install_bicep_trixie": { "image": "mcr.microsoft.com/devcontainers/base:trixie", "user": "vscode", "features": { @@ -68,7 +68,7 @@ "installUsingPython": true } } - }, + }, "install_with_python_3_12_bookworm": { "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bookworm", "user": "vscode", @@ -87,5 +87,20 @@ "installUsingPython": "true" } } + }, + "install_azcli_dotnet_dockerindocker_trixie": { + "image": "mcr.microsoft.com/devcontainers/dotnet:dev-10.0-preview-trixie", + "user": "vscode", + "features": { + "azure-cli": { + "version": "latest" + }, + "dotnet": { + "aspNetCoreRuntimeVersions": "8.0" + }, + "docker-in-docker": { + "moby": false + } + } } } \ No newline at end of file From 9fed2370e96255c6119874ad9ae44f7d68d0a0e6 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Thu, 20 Nov 2025 17:58:55 +0530 Subject: [PATCH 03/78] [common-utils] - Corrections in the installation of jq (#1510) --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 14850232d..bc57e3b44 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.5.4", + "version": "2.5.5", "name": "Common Utilities", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/common-utils", "description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.", diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index 68abe58c8..030995910 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -172,7 +172,7 @@ install_redhat_packages() { else echo "Unable to find 'tdnf', 'dnf', or 'yum' package manager. Exiting." exit 1 -fi + fi if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then package_list="${package_list} \ @@ -228,7 +228,7 @@ fi fi # Install EPEL repository if needed (required to install 'jq' for CentOS) - if ! ${install_cmd} -q list jq >/dev/null 2>&1; then + if [[ "${ID}" = "centos" ]] && ! rpm -q jq >/dev/null 2>&1; then ${install_cmd} -y install epel-release remove_epel="true" fi @@ -240,11 +240,18 @@ fi fi if [ -n "${package_list}" ]; then - ${install_cmd} -y install ${package_list} + echo "Packages to verify are installed: ${package_list}" + echo "Running ${install_cmd} install..." + if [ "${install_cmd}" = "dnf" ]; then + ${install_cmd} -y install --allowerasing ${package_list} + else + ${install_cmd} -y install ${package_list} + fi fi # Get to latest versions of all packages if [ "${UPGRADE_PACKAGES}" = "true" ]; then + echo "Running ${install_cmd} upgrade..." ${install_cmd} upgrade -y fi From 31f99a08a01840dd7c79d79744aca8bf3fe8afaf Mon Sep 17 00:00:00 2001 From: Mike Sarahan Date: Thu, 20 Nov 2025 06:53:48 -0600 Subject: [PATCH 04/78] allow opt-out of SSL installation (#1484) * allow opt-out of SSL installation * Bump common-utils version from 2.5.4 to 2.5.5 --- src/common-utils/devcontainer-feature.json | 5 +++ src/common-utils/install.sh | 1 + src/common-utils/main.sh | 37 ++++++++++++---------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index bc57e3b44..874093427 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -64,6 +64,11 @@ "type": "boolean", "default": false, "description": "Add packages from non-free Debian repository? (Debian only)" + }, + "installSsl": { + "type": "boolean", + "default": true, + "description": "Install SSL?" } } } diff --git a/src/common-utils/install.sh b/src/common-utils/install.sh index a89490347..313271d0a 100755 --- a/src/common-utils/install.sh +++ b/src/common-utils/install.sh @@ -18,6 +18,7 @@ USERNAME="${USERNAME:-"automatic"}" USER_UID="${UID:-"automatic"}" USER_GID="${GID:-"automatic"}" ADD_NON_FREE_PACKAGES="${NONFREEPACKAGES:-"false"}" +INSTALL_SSL="${INSTALLSSL:-"true"}" MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index 030995910..b0fd2f3b0 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -18,6 +18,7 @@ USERNAME="${USERNAME:-"automatic"}" USER_UID="${USERUID:-"automatic"}" USER_GID="${USERGID:-"automatic"}" ADD_NON_FREE_PACKAGES="${NONFREEPACKAGES:-"false"}" +INSTALL_SSL="${INSTALLSSL:-"true"}" MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" @@ -75,25 +76,27 @@ install_debian_packages() { manpages-dev \ init-system-helpers" - # Include libssl1.1 if available - if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then - package_list="${package_list} libssl1.1" - fi + if [ "${INSTALL_SSL}" = "true" ]; then + # Include libssl1.1 if available + if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then + package_list="${package_list} libssl1.1" + fi - # Include libssl3 if available - if [[ ! -z $(apt-cache --names-only search ^libssl3$) ]]; then - package_list="${package_list} libssl3" - fi + # Include libssl3 if available + if [[ ! -z $(apt-cache --names-only search ^libssl3$) ]]; then + package_list="${package_list} libssl3" + fi - # Include appropriate version of libssl1.0.x if available - local libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') - if [ "$(echo "$libssl_package" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then - if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then - # Debian 9 - package_list="${package_list} libssl1.0.2" - elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then - # Ubuntu 18.04 - package_list="${package_list} libssl1.0.0" + # Include appropriate version of libssl1.0.x if available + local libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') + if [ "$(echo "$libssl_package" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then + if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then + # Debian 9 + package_list="${package_list} libssl1.0.2" + elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then + # Ubuntu 18.04 + package_list="${package_list} libssl1.0.0" + fi fi fi From f7cc77bef2166e1b656ff825e4c320d0354ecccc Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 8 Dec 2025 10:15:14 +0100 Subject: [PATCH 05/78] Use /etc/profile.d to set PATH for dotnet tools (#1492) --- src/dotnet/devcontainer-feature.json | 4 ++-- src/dotnet/install.sh | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dotnet/devcontainer-feature.json b/src/dotnet/devcontainer-feature.json index 9389addaa..7d72c8cc0 100644 --- a/src/dotnet/devcontainer-feature.json +++ b/src/dotnet/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "dotnet", - "version": "2.4.0", + "version": "2.4.1", "name": "Dotnet CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/dotnet", "description": "This Feature installs the latest .NET SDK, which includes the .NET CLI and the shared runtime. Options are provided to choose a different version or additional versions.", @@ -45,7 +45,7 @@ }, "containerEnv": { "DOTNET_ROOT": "/usr/share/dotnet", - "PATH": "$PATH:$DOTNET_ROOT:~/.dotnet/tools", + "PATH": "$PATH:$DOTNET_ROOT", "DOTNET_RUNNING_IN_CONTAINER": "true", "DOTNET_USE_POLLING_FILE_WATCHER": "true" }, diff --git a/src/dotnet/install.sh b/src/dotnet/install.sh index bd85fb91a..a6bde1ebd 100644 --- a/src/dotnet/install.sh +++ b/src/dotnet/install.sh @@ -146,6 +146,13 @@ if [ ! -e /usr/bin/dotnet ]; then ln --symbolic "$DOTNET_ROOT/dotnet" /usr/bin/dotnet fi +# Add .NET Core SDK tools to PATH for bash and zsh users +# This is where 'dotnet tool install --global ' installs tools to +# Use single-quoted EOF to defer $PATH expansion until sourcing the file +cat << 'EOF' >> /etc/profile.d/dotnet.sh +export PATH="$PATH:$HOME/.dotnet/tools" +EOF + # Clean up rm -rf /var/lib/apt/lists/* rm -rf scripts From 545dbbd0da9528ed425cd812f6ca58bfcedbc1aa Mon Sep 17 00:00:00 2001 From: sireeshajonnalagadda Date: Mon, 8 Dec 2025 14:45:42 +0530 Subject: [PATCH 06/78] Adding azure-linux OS support for docker-in-docker feature (#1506) * Adding azure-linux OS support for docker-in-docker feature * test * Fixing issues and adding mariner * Fixing if * simplified code * addressing review comments --- .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 513 +++++++++++++++--- test/docker-in-docker/azure_linux.sh | 24 + .../docker-in-docker/azure_linux_docker_ce.sh | 113 ++++ test/docker-in-docker/cbl_mariner.sh | 35 ++ .../docker-in-docker/cbl_mariner_docker_ce.sh | 34 ++ test/docker-in-docker/scenarios.json | 40 ++ 7 files changed, 682 insertions(+), 79 deletions(-) create mode 100644 test/docker-in-docker/azure_linux.sh create mode 100644 test/docker-in-docker/azure_linux_docker_ce.sh create mode 100644 test/docker-in-docker/cbl_mariner.sh create mode 100644 test/docker-in-docker/cbl_mariner_docker_ce.sh diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 0450c664e..33b0ad31c 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": "2.12.4", + "version": "2.13.0", "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 46f1045ae..10f3ef06c 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -61,20 +61,45 @@ elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then USERNAME=root fi -apt_get_update() -{ - if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update -y - fi +# Package manager update function +pkg_mgr_update() { + case ${ADJUSTED_ID} in + debian) + if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update -y + fi + ;; + rhel) + if [ ${PKG_MGR_CMD} = "microdnf" ]; then + cache_check_dir="/var/cache/yum" + else + cache_check_dir="/var/cache/${PKG_MGR_CMD}" + fi + if [ "$(ls ${cache_check_dir}/* 2>/dev/null | wc -l)" = 0 ]; then + echo "Running ${PKG_MGR_CMD} makecache ..." + ${PKG_MGR_CMD} makecache + fi + ;; + esac } # Checks if packages are installed and installs them if not check_packages() { - if ! dpkg -s "$@" > /dev/null 2>&1; then - apt_get_update - apt-get -y install --no-install-recommends "$@" - fi + case ${ADJUSTED_ID} in + debian) + if ! dpkg -s "$@" > /dev/null 2>&1; then + pkg_mgr_update + apt-get -y install --no-install-recommends "$@" + fi + ;; + rhel) + if ! rpm -q "$@" > /dev/null 2>&1; then + pkg_mgr_update + ${PKG_MGR_CMD} -y install "$@" + fi + ;; + esac } # Figure out correct version of a three part version number is not passed @@ -189,11 +214,40 @@ get_github_api_repo_url() { # Ensure apt is in non-interactive to avoid prompts export DEBIAN_FRONTEND=noninteractive - # Source /etc/os-release to get OS info . /etc/os-release -# Fetch host/container arch. -architecture="$(dpkg --print-architecture)" + +# Determine adjusted ID and package manager +if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then + ADJUSTED_ID="debian" + PKG_MGR_CMD="apt-get" + # Use dpkg for Debian-based systems + architecture="$(dpkg --print-architecture 2>/dev/null || uname -m)" +elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "azurelinux" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"azurelinux"* || "${ID_LIKE}" = *"mariner"* ]]; then + ADJUSTED_ID="rhel" + # Determine the appropriate package manager for RHEL-based systems + for pkg_mgr in tdnf dnf microdnf yum; do + if command -v "$pkg_mgr" >/dev/null 2>&1; then + PKG_MGR_CMD="$pkg_mgr" + break + fi + done + + if [ -z "${PKG_MGR_CMD}" ]; then + err "Unable to find a supported package manager (tdnf, dnf, microdnf, yum)" + exit 1 + fi + + architecture="$(rpm --eval '%{_arch}' 2>/dev/null || uname -m)" +else + err "Linux distro ${ID} not supported." + exit 1 +fi + +# Azure Linux specific setup +if [ "${ID}" = "azurelinux" ]; then + VERSION_CODENAME="azurelinux${VERSION_ID}" +fi # Prevent attempting to install Moby on Debian trixie (packages removed) if [ "${USE_MOBY}" = "true" ] && [ "${ID}" = "debian" ] && [ "${VERSION_CODENAME}" = "trixie" ]; then @@ -204,60 +258,199 @@ fi # Check if distro is supported if [ "${USE_MOBY}" = "true" ]; then - if [[ "${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then - err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS distribution" - err "Supported distributions include: ${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" - exit 1 + if [ "${ADJUSTED_ID}" = "debian" ]; then + if [[ "${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then + err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS distribution" + err "Supported distributions include: ${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" + exit 1 + fi + echo "(*) ${VERSION_CODENAME} is supported for Moby installation - setting up Microsoft repository" + elif [ "${ADJUSTED_ID}" = "rhel" ]; then + if [ "${ID}" = "azurelinux" ] || [ "${ID}" = "mariner" ]; then + echo " (*) ${ID} ${VERSION_ID} detected - using Microsoft repositories for Moby packages" + else + echo "RHEL-based system (${ID}) detected - Moby packages may require additional configuration" + fi fi - echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}'" else - if [[ "${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then - err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, please choose a compatible OS distribution" - err "Supported distributions include: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" - exit 1 + if [ "${ADJUSTED_ID}" = "debian" ]; then + if [[ "${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then + err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, please choose a compatible OS distribution" + err "Supported distributions include: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" + exit 1 + fi + echo "(*) ${VERSION_CODENAME} is supported for Docker CE installation (supported: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}) - setting up Docker repository" + elif [ "${ADJUSTED_ID}" = "rhel" ]; then + + echo "RHEL-based system (${ID}) detected - using Docker CE packages" fi - echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}'" fi -# Install dependencies -check_packages apt-transport-https curl ca-certificates pigz iptables gnupg2 dirmngr wget jq -if ! type git > /dev/null 2>&1; then +# Install base dependencies +base_packages="curl ca-certificates pigz iptables gnupg2 wget jq" +case ${ADJUSTED_ID} in + debian) + check_packages apt-transport-https $base_packages dirmngr + ;; + rhel) + check_packages $base_packages tar gawk shadow-utils policycoreutils procps-ng systemd-libs systemd-devel + + ;; +esac + +# Install git if not already present +if ! command -v git >/dev/null 2>&1; then check_packages git fi -# Swap to legacy iptables for compatibility -if type iptables-legacy > /dev/null 2>&1; then +# Swap to legacy iptables for compatibility (Debian only) +if [ "${ADJUSTED_ID}" = "debian" ] && type iptables-legacy > /dev/null 2>&1; then update-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy fi - - -# Set up the necessary apt repos (either Microsoft's or Docker's) +# Set up the necessary repositories if [ "${USE_MOBY}" = "true" ]; then - # Name of open source engine/cli engine_package_name="moby-engine" cli_package_name="moby-cli" - # Import key safely and import Microsoft apt repo - { - curl -sSL ${MICROSOFT_GPG_KEYS_URI} - curl -sSL ${MICROSOFT_GPG_KEYS_ROLLING_URI} - } | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg - echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list + case ${ADJUSTED_ID} in + debian) + # Import key safely and import Microsoft apt repo + { + curl -sSL ${MICROSOFT_GPG_KEYS_URI} + curl -sSL ${MICROSOFT_GPG_KEYS_ROLLING_URI} + } | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg + echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list + ;; + rhel) + echo "(*) ${ID} detected - checking for Moby packages..." + + # Check if moby packages are available in default repos + if ${PKG_MGR_CMD} list available moby-engine >/dev/null 2>&1; then + echo "(*) Using built-in ${ID} Moby packages" + else + case "${ID}" in + azurelinux) + echo "(*) Moby packages not found in Azure Linux repositories" + echo "(*) For Azure Linux, Docker CE ('moby': false) is recommended" + err "Moby packages are not available for Azure Linux ${VERSION_ID}." + err "Recommendation: Use '\"moby\": false' to install Docker CE instead." + exit 1 + ;; + mariner) + echo "(*) Adding Microsoft repository for CBL-Mariner..." + # Add Microsoft repository if packages aren't available locally + curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /etc/pki/rpm-gpg/microsoft.gpg + cat > /etc/yum.repos.d/microsoft.repo << EOF +[microsoft] +name=Microsoft Repository +baseurl=https://packages.microsoft.com/repos/microsoft-cbl-mariner-2.0-prod-base/ +enabled=1 +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/microsoft.gpg +EOF + # Verify packages are available after adding repo + pkg_mgr_update + if ! ${PKG_MGR_CMD} list available moby-engine >/dev/null 2>&1; then + echo "(*) Moby packages not found in Microsoft repository either" + err "Moby packages are not available for CBL-Mariner ${VERSION_ID}." + err "Recommendation: Use '\"moby\": false' to install Docker CE instead." + exit 1 + fi + ;; + *) + err "Moby packages are not available for ${ID}. Please use 'moby': false option." + exit 1 + ;; + esac + fi + ;; + esac else # Name of licensed engine/cli engine_package_name="docker-ce" cli_package_name="docker-ce-cli" - - # Import key safely and import Docker apt repo - curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list + case ${ADJUSTED_ID} in + debian) + curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list + ;; + rhel) + # Docker CE repository setup for RHEL-based systems + setup_docker_ce_repo() { + curl -fsSL https://download.docker.com/linux/centos/gpg > /etc/pki/rpm-gpg/docker-ce.gpg + cat > /etc/yum.repos.d/docker-ce.repo << EOF +[docker-ce-stable] +name=Docker CE Stable +baseurl=https://download.docker.com/linux/centos/9/\$basearch/stable +enabled=1 +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/docker-ce.gpg +skip_if_unavailable=1 +module_hotfixes=1 +EOF + } + install_azure_linux_deps() { + echo "(*) Installing device-mapper libraries for Docker CE..." + [ "${ID}" != "mariner" ] && ${PKG_MGR_CMD} -y install device-mapper-libs 2>/dev/null || echo "(*) Device-mapper install failed, proceeding" + echo "(*) Installing additional Docker CE dependencies..." + ${PKG_MGR_CMD} -y install libseccomp libtool-ltdl systemd-libs libcgroup tar xz || { + echo "(*) Some optional dependencies could not be installed, continuing..." + } + } + setup_selinux_context() { + if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce 2>/dev/null)" != "Disabled" ]; then + echo "(*) Creating minimal SELinux context for Docker compatibility..." + mkdir -p /etc/selinux/targeted/contexts/files/ 2>/dev/null || true + echo "/var/lib/docker(/.*)? system_u:object_r:container_file_t:s0" >> /etc/selinux/targeted/contexts/files/file_contexts.local 2>/dev/null || true + fi + } + + # Special handling for RHEL Docker CE installation + case "${ID}" in + azurelinux|mariner) + echo "(*) ${ID} detected" + echo "(*) Note: Moby packages work better on Azure Linux. Consider using 'moby': true" + echo "(*) Setting up Docker CE repository..." + + setup_docker_ce_repo + install_azure_linux_deps + + if [ "${USE_MOBY}" != "true" ]; then + echo "(*) Docker CE installation for Azure Linux - skipping container-selinux" + echo "(*) Note: SELinux policies will be minimal but Docker will function normally" + setup_selinux_context + else + echo "(*) Using Moby - container-selinux not required" + fi + ;; + *) + # Standard RHEL/CentOS/Fedora approach + if command -v dnf >/dev/null 2>&1; then + dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + elif command -v yum-config-manager >/dev/null 2>&1; then + yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + else + # Manual fallback + setup_docker_ce_repo + fi + ;; + esac + ;; + esac fi -# Refresh apt lists -apt-get update +# Refresh package database +case ${ADJUSTED_ID} in + debian) + apt-get update + ;; + rhel) + pkg_mgr_update + ;; +esac # Soft version matching if [ "${DOCKER_VERSION}" = "latest" ] || [ "${DOCKER_VERSION}" = "lts" ] || [ "${DOCKER_VERSION}" = "stable" ]; then @@ -265,6 +458,8 @@ if [ "${DOCKER_VERSION}" = "latest" ] || [ "${DOCKER_VERSION}" = "lts" ] || [ "$ engine_version_suffix="" cli_version_suffix="" else + case ${ADJUSTED_ID} in + debian) # Fetch a valid version from the apt-cache (eg: the Microsoft repo appends +azure, breakfix, etc...) docker_version_dot_escaped="${DOCKER_VERSION//./\\.}" docker_version_dot_plus_escaped="${docker_version_dot_escaped//+/\\+}" @@ -279,8 +474,27 @@ else apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+' exit 1 fi - echo "engine_version_suffix ${engine_version_suffix}" - echo "cli_version_suffix ${cli_version_suffix}" + ;; +rhel) + # For RHEL-based systems, use dnf/yum to find versions + docker_version_escaped="${DOCKER_VERSION//./\\.}" + set +e # Don't exit if finding version fails - will handle gracefully + if [ "${USE_MOBY}" = "true" ]; then + available_versions=$(${PKG_MGR_CMD} list --available moby-engine 2>/dev/null | grep -v "Available Packages" | awk '{print $2}' | grep -E "^${docker_version_escaped}" | head -1) + else + available_versions=$(${PKG_MGR_CMD} list --available docker-ce 2>/dev/null | grep -v "Available Packages" | awk '{print $2}' | grep -E "^${docker_version_escaped}" | head -1) + fi + set -e + if [ -n "${available_versions}" ]; then + engine_version_suffix="-${available_versions}" + cli_version_suffix="-${available_versions}" + else + echo "(*) Exact version ${DOCKER_VERSION} not found, using latest available" + engine_version_suffix="" + cli_version_suffix="" + fi + ;; + esac fi # Version matching for moby-buildx @@ -289,6 +503,8 @@ if [ "${USE_MOBY}" = "true" ]; then # Empty, meaning grab whatever "latest" is in apt repo buildx_version_suffix="" else + case ${ADJUSTED_ID} in + debian) buildx_version_dot_escaped="${MOBY_BUILDX_VERSION//./\\.}" buildx_version_dot_plus_escaped="${buildx_version_dot_escaped//+/\\+}" buildx_version_regex="^(.+:)?${buildx_version_dot_plus_escaped}([\\.\\+ ~:-]|$)" @@ -300,6 +516,21 @@ if [ "${USE_MOBY}" = "true" ]; then apt-cache madison moby-buildx | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+' exit 1 fi + ;; + rhel) + # For RHEL-based systems, try to find buildx version or use latest + buildx_version_escaped="${MOBY_BUILDX_VERSION//./\\.}" + set +e + available_buildx=$(${PKG_MGR_CMD} list --available moby-buildx 2>/dev/null | grep -v "Available Packages" | awk '{print $2}' | grep -E "^${buildx_version_escaped}" | head -1) + set -e + if [ -n "${available_buildx}" ]; then + buildx_version_suffix="-${available_buildx}" + else + echo "(*) Exact buildx version ${MOBY_BUILDX_VERSION} not found, using latest available" + buildx_version_suffix="" + fi + ;; + esac echo "buildx_version_suffix ${buildx_version_suffix}" fi fi @@ -308,26 +539,131 @@ fi if type docker > /dev/null 2>&1 && type dockerd > /dev/null 2>&1; then echo "Docker / Moby CLI and Engine already installed." else - if [ "${USE_MOBY}" = "true" ]; then - # Install engine - set +e # Handle error gracefully - apt-get -y install --no-install-recommends moby-cli${cli_version_suffix} moby-buildx${buildx_version_suffix} moby-engine${engine_version_suffix} - exit_code=$? - set -e - - if [ ${exit_code} -ne 0 ]; then - err "Packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-24.04')." - exit 1 - fi - - # Install compose - apt-get -y install --no-install-recommends moby-compose || err "Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." - else - apt-get -y install --no-install-recommends docker-ce-cli${cli_version_suffix} docker-ce${engine_version_suffix} - # Install compose - apt-mark hold docker-ce docker-ce-cli - apt-get -y install --no-install-recommends docker-compose-plugin || echo "(*) Package docker-compose-plugin (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." - fi + case ${ADJUSTED_ID} in + debian) + if [ "${USE_MOBY}" = "true" ]; then + # Install engine + set +e # Handle error gracefully + apt-get -y install --no-install-recommends moby-cli${cli_version_suffix} moby-buildx${buildx_version_suffix} moby-engine${engine_version_suffix} + exit_code=$? + set -e + + if [ ${exit_code} -ne 0 ]; then + err "Packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-24.04')." + exit 1 + fi + + # Install compose + apt-get -y install --no-install-recommends moby-compose || err "Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + else + apt-get -y install --no-install-recommends docker-ce-cli${cli_version_suffix} docker-ce${engine_version_suffix} + # Install compose + apt-mark hold docker-ce docker-ce-cli + apt-get -y install --no-install-recommends docker-compose-plugin || echo "(*) Package docker-compose-plugin (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + fi + ;; + rhel) + if [ "${USE_MOBY}" = "true" ]; then + set +e # Handle error gracefully + ${PKG_MGR_CMD} -y install moby-cli${cli_version_suffix} moby-engine${engine_version_suffix} + exit_code=$? + set -e + + if [ ${exit_code} -ne 0 ]; then + err "Packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version." + exit 1 + fi + + # Install compose + if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then + ${PKG_MGR_CMD} -y install moby-compose || echo "(*) Package moby-compose not available for ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + fi + else + # Special handling for Azure Linux Docker CE installation + if [ "${ID}" = "azurelinux" ] || [ "${ID}" = "mariner" ]; then + echo "(*) Installing Docker CE on Azure Linux (bypassing container-selinux dependency)..." + + # Use rpm with --force and --nodeps for Azure Linux + set +e # Don't exit on error for this section + ${PKG_MGR_CMD} -y install docker-ce${cli_version_suffix} docker-ce-cli${engine_version_suffix} containerd.io + install_result=$? + set -e + + if [ $install_result -ne 0 ]; then + echo "(*) Standard installation failed, trying manual installation..." + + echo "(*) Standard installation failed, trying manual installation..." + + # Create directory for downloading packages + mkdir -p /tmp/docker-ce-install + + # Download packages manually using curl since tdnf doesn't support download + echo "(*) Downloading Docker CE packages manually..." + + # Get the repository baseurl + repo_baseurl="https://download.docker.com/linux/centos/9/x86_64/stable" + + # Download packages directly + cd /tmp/docker-ce-install + + # Get package names with versions + if [ -n "${cli_version_suffix}" ]; then + docker_ce_version="${cli_version_suffix#-}" + docker_cli_version="${engine_version_suffix#-}" + else + # Get latest version from repository + docker_ce_version="latest" + fi + + echo "(*) Attempting to download Docker CE packages from repository..." + + # Try to download latest packages if specific version fails + if ! curl -fsSL "${repo_baseurl}/Packages/docker-ce-${docker_ce_version}.el9.x86_64.rpm" -o docker-ce.rpm 2>/dev/null; then + # Fallback: try to get latest available version + echo "(*) Specific version not found, trying latest..." + latest_docker=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'docker-ce-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1) + latest_cli=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'docker-ce-cli-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1) + latest_containerd=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'containerd\.io-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1) + + if [ -n "${latest_docker}" ]; then + curl -fsSL "${repo_baseurl}/Packages/${latest_docker}" -o docker-ce.rpm + curl -fsSL "${repo_baseurl}/Packages/${latest_cli}" -o docker-ce-cli.rpm + curl -fsSL "${repo_baseurl}/Packages/${latest_containerd}" -o containerd.io.rpm + else + echo "(*) ERROR: Could not find Docker CE packages in repository" + echo "(*) Please check repository configuration or use 'moby': true" + exit 1 + fi + fi + # Install systemd libraries required by Docker CE + echo "(*) Installing systemd libraries required by Docker CE..." + ${PKG_MGR_CMD} -y install systemd-libs || ${PKG_MGR_CMD} -y install systemd-devel || { + echo "(*) WARNING: Could not install systemd libraries" + echo "(*) Docker may fail to start without these" + } + + # Install with rpm --force --nodeps + echo "(*) Installing Docker CE packages with dependency override..." + rpm -Uvh --force --nodeps *.rpm + + # Cleanup + cd / + rm -rf /tmp/docker-ce-install + + echo "(*) Docker CE installation completed with dependency bypass" + echo "(*) Note: Some SELinux functionality may be limited without container-selinux" + fi + else + # Standard installation for other RHEL-based systems + ${PKG_MGR_CMD} -y install docker-ce${cli_version_suffix} docker-ce-cli${engine_version_suffix} containerd.io + fi + # Install compose + if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then + ${PKG_MGR_CMD} -y install docker-compose-plugin || echo "(*) Package docker-compose-plugin not available for ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + fi + fi + ;; + esac fi echo "Finished installing docker / moby!" @@ -348,11 +684,11 @@ fallback_compose(){ # If 'docker-compose' command is to be included if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then case "${architecture}" in - amd64) target_compose_arch=x86_64 ;; - arm64) target_compose_arch=aarch64 ;; - *) - echo "(!) Docker in docker does not support machine architecture '$architecture'. Please use an x86-64 or ARM64 machine." - exit 1 + amd64|x86_64) target_compose_arch=x86_64 ;; + arm64|aarch64) target_compose_arch=aarch64 ;; + *) + echo "(!) Docker in docker does not support machine architecture '$architecture'. Please use an x86-64 or ARM64 machine." + exit 1 esac docker_compose_path="/usr/local/bin/docker-compose" @@ -408,19 +744,32 @@ fallback_compose-switch() { echo -e "\n(!) Failed to fetch the latest artifacts for compose-switch v${compose_switch_version}..." get_previous_version "$url" "$repo_url" compose_switch_version echo -e "\nAttempting to install v${compose_switch_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 + curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${target_switch_arch}" -o /usr/local/bin/compose-switch } - # Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation if [ "${INSTALL_DOCKER_COMPOSE_SWITCH}" = "true" ] && ! type compose-switch > /dev/null 2>&1; then if type docker-compose > /dev/null 2>&1; then echo "(*) Installing compose-switch..." - current_compose_path="$(which docker-compose)" + current_compose_path="$(command -v docker-compose)" target_compose_path="$(dirname "${current_compose_path}")/docker-compose-v1" compose_switch_version="latest" 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" - curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch || fallback_compose-switch "$compose_switch_url" + 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" + fi + set -e + + # Map architecture for compose-switch downloads + case "${architecture}" in + amd64|x86_64) target_switch_arch=amd64 ;; + arm64|aarch64) target_switch_arch=arm64 ;; + *) target_switch_arch=${architecture} ;; + esac + curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${target_switch_arch}" -o /usr/local/bin/compose-switch || fallback_compose-switch "$compose_switch_url" chmod +x /usr/local/bin/compose-switch # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11 # Setup v1 CLI as alternative in addition to compose-switch (which maps to v2) @@ -453,7 +802,7 @@ fallback_buildx() { local repo_url=$(get_github_api_repo_url "$url") echo -e "\n(!) Failed to fetch the latest artifacts for docker buildx v${buildx_version}..." get_previous_version "$url" "$repo_url" buildx_version - buildx_file_name="buildx-v${buildx_version}.linux-${architecture}" + buildx_file_name="buildx-v${buildx_version}.linux-${target_buildx_arch}" echo -e "\nAttempting to install v${buildx_version}" wget https://github.com/docker/buildx/releases/download/v${buildx_version}/${buildx_file_name} } @@ -463,8 +812,16 @@ if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then docker_buildx_url="https://github.com/docker/buildx" find_version_from_git_tags buildx_version "$docker_buildx_url" "refs/tags/v" echo "(*) Installing buildx ${buildx_version}..." - buildx_file_name="buildx-v${buildx_version}.linux-${architecture}" - + + # Map architecture for buildx downloads + case "${architecture}" in + amd64|x86_64) target_buildx_arch=amd64 ;; + arm64|aarch64) target_buildx_arch=arm64 ;; + *) target_buildx_arch=${architecture} ;; + esac + + buildx_file_name="buildx-v${buildx_version}.linux-${target_buildx_arch}" + cd /tmp wget https://github.com/docker/buildx/releases/download/v${buildx_version}/${buildx_file_name} || fallback_buildx "$docker_buildx_url" diff --git a/test/docker-in-docker/azure_linux.sh b/test/docker-in-docker/azure_linux.sh new file mode 100644 index 000000000..ca5f51997 --- /dev/null +++ b/test/docker-in-docker/azure_linux.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# OS identification (optional, can fail gracefully) +check "azure os detection" bash -c "cat /etc/os-release | grep -i azure || echo 'Not Azure Linux, but test can continue'" + +# Core Docker functionality +check "docker version" docker --version +check "docker daemon running" docker info + +# Docker init script (if using docker-in-docker feature) +check "docker init script exists" test -f "/usr/local/share/docker-init.sh" + +# Basic functionality test +check "docker container test" docker run --rm alpine echo "test successful" + +# The main Azure Linux specific test - DNS flag should NOT be present +check "dns flag should not be present" test ! "$(ps -ax | grep -v grep | grep -E 'dockerd.+\-\-dns')" + +# Report result +reportResults \ No newline at end of file diff --git a/test/docker-in-docker/azure_linux_docker_ce.sh b/test/docker-in-docker/azure_linux_docker_ce.sh new file mode 100644 index 000000000..fd1b72c4c --- /dev/null +++ b/test/docker-in-docker/azure_linux_docker_ce.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +echo "=== Azure Linux Docker CE Test ===" + +echo "1. Verifying OS identification..." +cat /etc/os-release | grep -i azure + +echo "2. Checking Docker installation..." +docker --version || { + echo "ERROR: Docker is not installed" + exit 1 +} + +echo "3. Checking if Docker binaries exist..." +which dockerd || echo "dockerd not in PATH" +ls -la /usr/bin/docker* || echo "No docker binaries in /usr/bin" +ls -la /usr/local/bin/docker* || echo "No docker binaries in /usr/local/bin" + +echo "4. Checking Docker service files..." +ls -la /etc/systemd/system/docker* || echo "No systemd docker files" +ls -la /usr/lib/systemd/system/docker* || echo "No system docker service files" + +echo "6. Checking Docker daemon status..." +if ! docker info >/dev/null 2>&1; then + echo "Docker daemon not running, starting it..." + + # Check if docker-init script exists + if [ -f "/usr/local/share/docker-init.sh" ]; then + echo "Starting Docker using docker-init.sh..." + + # Clear any existing log + rm -f /tmp/dockerd.log + + # Start docker-init in background + nohup /usr/local/share/docker-init.sh > /tmp/docker-init.log 2>&1 & + + # Wait for Docker to start with better debugging + echo "Waiting for Docker daemon to start..." + for i in {1..30}; do + if docker info >/dev/null 2>&1; then + echo "Docker daemon started successfully" + break + fi + echo "Waiting... ($i/30)" + + # Show dockerd logs if available + if [ -f "/tmp/dockerd.log" ]; then + echo "--- Recent dockerd.log entries ---" + tail -5 /tmp/dockerd.log || echo "Could not read dockerd.log" + echo "--- End of dockerd.log ---" + fi + + sleep 2 + done + + # Final check with detailed error reporting + if ! docker info >/dev/null 2>&1; then + echo "ERROR: Docker daemon failed to start after 60 seconds" + echo "" + echo "=== DEBUGGING INFORMATION ===" + echo "" + echo "1. Docker init log:" + cat /tmp/docker-init.log 2>/dev/null || echo "No docker-init.log found" + echo "" + echo "2. Docker daemon log:" + cat /tmp/dockerd.log 2>/dev/null || echo "No dockerd.log found" + echo "" + echo "3. Process list:" + ps aux | grep -E "(docker|containerd)" | grep -v grep || echo "No docker/containerd processes found" + echo "" + echo "4. Network interfaces:" + ip addr show || ifconfig || echo "Could not get network info" + echo "" + echo "5. Mount points:" + mount | grep -E "(docker|container)" || echo "No docker-related mounts" + echo "" + echo "6. SELinux status:" + if command -v getenforce >/dev/null 2>&1; then + getenforce || echo "SELinux command failed" + else + echo "SELinux tools not available" + fi + echo "" + echo "7. Available storage:" + df -h /var/lib/docker 2>/dev/null || df -h / || echo "Could not check storage" + echo "" + echo "8. System resources:" + free -h || echo "Could not check memory" + echo "" + echo "=== END DEBUGGING ===" + exit 1 + fi + else + echo "ERROR: docker-init.sh not found at /usr/local/share/docker-init.sh" + ls -la /usr/local/share/ || echo "Could not list /usr/local/share/" + exit 1 + fi +else + echo "Docker daemon is already running" +fi + +echo "7. Testing basic Docker functionality..." +docker info | head -10 + +echo "8. Testing container execution..." +docker run --rm alpine echo "Basic container test successful" + +echo "=== Docker CE test completed successfully ===" \ No newline at end of file diff --git a/test/docker-in-docker/cbl_mariner.sh b/test/docker-in-docker/cbl_mariner.sh new file mode 100644 index 000000000..44a8ee38e --- /dev/null +++ b/test/docker-in-docker/cbl_mariner.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Simple test script for cbl_mariner scenario (Moby = true) +# Run with: sudo bash script_cbl_mariner.sh + +set -e + +echo "=== Testing cbl_mariner scenario (Moby) ===" + +# Set environment variables for the scenario +export VERSION="latest" +export MOBY="true" +export AZUREDNSAUTODETECTION="false" + +# Source OS info +. /etc/os-release +echo "OS: $ID $VERSION_ID" + +# Check package manager +if type tdnf > /dev/null 2>&1; then + echo "Using tdnf" +else + echo "ERROR: tdnf not found" + exit 1 +fi + +# Validate +if command -v docker > /dev/null 2>&1; then + docker --version + echo "SUCCESS: Docker installed" +else + echo "ERROR: Docker not installed" + exit 1 +fi + +echo "=== cbl_mariner test passed ===" \ No newline at end of file diff --git a/test/docker-in-docker/cbl_mariner_docker_ce.sh b/test/docker-in-docker/cbl_mariner_docker_ce.sh new file mode 100644 index 000000000..24788377b --- /dev/null +++ b/test/docker-in-docker/cbl_mariner_docker_ce.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# test script for cbl_mariner_docker_ce scenario (Moby = false) + +set -e + +echo "=== Testing cbl_mariner_docker_ce scenario (Docker CE) ===" + +# Set environment variables for the scenario +export VERSION="latest" +export MOBY="false" +export AZUREDNSAUTODETECTION="false" + +# Source OS info +. /etc/os-release +echo "OS: $ID $VERSION_ID" + +# Check package manager +if type tdnf > /dev/null 2>&1; then + echo "Using tdnf" +else + echo "ERROR: tdnf not found" + exit 1 +fi + +# Validate +if command -v docker > /dev/null 2>&1; then + docker --version + echo "SUCCESS: Docker installed" +else + echo "ERROR: Docker not installed" + exit 1 +fi + +echo "=== cbl_mariner_docker_ce test passed ===" \ No newline at end of file diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index 3b5dce013..334cb7798 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -177,5 +177,45 @@ }, "remoteUser": "vscode", "onCreateCommand": "docker ps && sleep 5s && docker ps" + }, + "azure_linux": { + "image": "mcr.microsoft.com/azurelinux/base/core:3.0", + "features": { + "docker-in-docker": { + "version": "latest", + "moby": "true", + "azureDnsAutoDetection": false + } + } + }, + "azure_linux_docker_ce": { + "image": "mcr.microsoft.com/azurelinux/base/core:3.0", + "features": { + "docker-in-docker": { + "version": "latest", + "moby": "false", + "azureDnsAutoDetection": false + } + } + }, + "cbl_mariner": { + "image": "mcr.microsoft.com/cbl-mariner/base/core:2.0", + "features": { + "docker-in-docker": { + "version": "latest", + "moby": "true", + "azureDnsAutoDetection": false + } + } + }, + "cbl_mariner_docker_ce": { + "image": "mcr.microsoft.com/cbl-mariner/base/core:2.0", + "features": { + "docker-in-docker": { + "version": "latest", + "moby": "false", + "azureDnsAutoDetection": false + } + } } } From b950373cae63de22c6f0984bb0ff2f76f47a2f21 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Mon, 8 Dec 2025 12:14:18 -0500 Subject: [PATCH 07/78] Add feature for copilot-cli (#1523) * Add feature for copilot-cli * Add copilot-cli to CI workflows for testing * Add check for supported arch and fix remote name for amd64 * Remove none * Allow pinning a specific version * Add leading v for pinned versions --- .github/workflows/test-all.yaml | 2 + .github/workflows/test-pr.yaml | 1 + src/copilot-cli/NOTES.md | 7 ++ src/copilot-cli/README.md | 31 +++++++++ src/copilot-cli/devcontainer-feature.json | 33 +++++++++ src/copilot-cli/install.sh | 82 +++++++++++++++++++++++ test/copilot-cli/test.sh | 5 ++ 7 files changed, 161 insertions(+) create mode 100644 src/copilot-cli/NOTES.md create mode 100644 src/copilot-cli/README.md create mode 100644 src/copilot-cli/devcontainer-feature.json create mode 100755 src/copilot-cli/install.sh create mode 100644 test/copilot-cli/test.sh diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index 7e4c343bd..ea4fe8728 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -17,6 +17,7 @@ jobs: "azure-cli", "common-utils", "conda", + "copilot-cli", "desktop-lite", "docker-outside-of-docker", "docker-in-docker", @@ -70,6 +71,7 @@ jobs: "azure-cli", "common-utils", "conda", + "copilot-cli", "desktop-lite", "docker-outside-of-docker", "docker-in-docker", diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index c5930d97e..645d38109 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -17,6 +17,7 @@ jobs: azure-cli: ./**/azure-cli/** common-utils: ./**/common-utils/** conda: ./**/conda/** + copilot-cli: ./**/copilot-cli/** desktop-lite: ./**/desktop-lite/** docker-outside-of-docker: ./**/docker-outside-of-docker/** docker-in-docker: ./**/docker-in-docker/** diff --git a/src/copilot-cli/NOTES.md b/src/copilot-cli/NOTES.md new file mode 100644 index 000000000..c5393ab5a --- /dev/null +++ b/src/copilot-cli/NOTES.md @@ -0,0 +1,7 @@ + + +## OS Support + +This Feature should work on recent versions of Debian/Ubuntu-based distributions. + +`bash` is required to execute the `install.sh` script. diff --git a/src/copilot-cli/README.md b/src/copilot-cli/README.md new file mode 100644 index 000000000..3b7fc3be5 --- /dev/null +++ b/src/copilot-cli/README.md @@ -0,0 +1,31 @@ + +# GitHub Copilot CLI (copilot-cli) + +Installs the GitHub Copilot CLI. Auto-detects latest version and installs needed dependencies. + +## Example Usage + +```json +"features": { + "ghcr.io/devcontainers/features/copilot-cli:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Select version of the GitHub Copilot CLI, if not latest. | string | latest | + + + +## OS Support + +This Feature should work on recent versions of Debian/Ubuntu-based distributions. + +`bash` is required to execute the `install.sh` script. + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/copilot-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ \ No newline at end of file diff --git a/src/copilot-cli/devcontainer-feature.json b/src/copilot-cli/devcontainer-feature.json new file mode 100644 index 000000000..ab433e2ae --- /dev/null +++ b/src/copilot-cli/devcontainer-feature.json @@ -0,0 +1,33 @@ +{ + "id": "copilot-cli", + "version": "1.0.0", + "name": "GitHub Copilot CLI", + "documentationURL": "https://github.com/devcontainers/features/tree/main/src/copilot-cli", + "description": "Installs the GitHub Copilot CLI.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "prerelease" + ], + "default": "latest", + "description": "Select version of the GitHub Copilot CLI, if not latest." + } + }, + "customizations": { + "vscode": { + "settings": { + "github.copilot.chat.codeGeneration.instructions": [ + { + "text": "This dev container includes the GitHub Copilot CLI (`copilot`), which is pre-installed and available on the `PATH`." + } + ] + } + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} + diff --git a/src/copilot-cli/install.sh b/src/copilot-cli/install.sh new file mode 100755 index 000000000..7250fa642 --- /dev/null +++ b/src/copilot-cli/install.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/devcontainers/features/blob/main/src/copilot-cli/README.md +# Maintainer: The VS Code and Codespaces Teams + +CLI_VERSION=${VERSION:-"latest"} + +set -e + +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.' + exit 1 +fi + +apt_get_update() { + if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update -y + fi +} + +# Checks if packages are installed and installs them if not +check_packages() { + if ! dpkg -s "$@" > /dev/null 2>&1; then + apt_get_update + apt-get -y install --no-install-recommends "$@" + fi +} + +download_from_github() { + local release_url=$1 + echo "Downloading GitHub Copilot CLI from ${release_url}..." + + mkdir -p /tmp/copilotcli + pushd /tmp/copilotcli + wget --show-progress --progress=dot:giga ${release_url} + # curl -fL# -O ${release_url} + tar -xzf /tmp/copilotcli/${cli_filename} + mv copilot /usr/local/bin/copilot + popd + rm -rf /tmp/copilotcli +} + +install_using_github() { + check_packages wget tar ca-certificates git + echo "Finished setting up dependencies" + arch=$(dpkg --print-architecture) + if [ "${arch}" = "amd64" ]; then + arch="x64" + fi + if [ "${arch}" != "x64" ] && [ "${arch}" != "arm64" ]; then + echo "Unsupported architecture: ${arch}" >&2 + exit 1 + fi + cli_filename="copilot-linux-${arch}.tar.gz" + echo "Installing GitHub Copilot CLI for ${arch} architecture: ${cli_filename}" + + # Install latest + 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="$(git ls-remote --tags https://github.com/github/copilot-cli | tail -1 | awk -F/ '{print $NF}')" + download_from_github "https://github.com/github/copilot-cli/releases/download/${prerelease_version}/${cli_filename}" + else + # Install specific version + # Add leading v to version if it doesn't start with v + if [[ ! "${CLI_VERSION}" =~ ^v[0-9] ]]; then + CLI_VERSION="v${CLI_VERSION}" + fi + download_from_github "https://github.com/github/copilot-cli/releases/download/${CLI_VERSION}/${cli_filename}" + fi +} + +# Install the GitHub Copilot CLI +echo "Downloading GitHub Copilot CLI..." + +install_using_github + diff --git a/test/copilot-cli/test.sh b/test/copilot-cli/test.sh new file mode 100644 index 000000000..d1d624cda --- /dev/null +++ b/test/copilot-cli/test.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +echo "Checking if GitHub Copilot is installed..." +which copilot +copilot -v \ No newline at end of file From b0188f0a5ef98f1c3217b6c0fe14c3ee472fad68 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 12 Dec 2025 13:48:40 +0100 Subject: [PATCH 08/78] Use postStartCommand to avoid creating a .gitconfig (#1391) Use postStartCommand to avoid creating a new .gitconfig --- src/common-utils/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common-utils/README.md b/src/common-utils/README.md index b8e37a8c8..3d21e8b3f 100644 --- a/src/common-utils/README.md +++ b/src/common-utils/README.md @@ -38,7 +38,7 @@ these images have already allocated UID & GID 1000. Attempting to add this Featu By default, this script provides a custom command prompt that includes information about the git repository for the current folder. However, with certain large repositories, this can result in a slow command prompt due to the performance of needed git operations. -For performance reasons, a "dirty" indicator that tells you whether or not there are uncommitted changes is disabled by default. You can opt to turn this on for smaller repositories by entering the following in a terminal or adding it to your `postCreateCommand`: +For performance reasons, a "dirty" indicator that tells you whether or not there are uncommitted changes is disabled by default. You can opt to turn this on for smaller repositories by entering the following in a terminal or adding it to your `postStartCommand`: ```bash git config devcontainers-theme.show-dirty 1 From e9cfd3fec55ab1cd40bb17d7b6eb19bb4100f292 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 13 Jan 2026 21:39:43 +0530 Subject: [PATCH 09/78] [dotnet] - Removing dotnet 10 preview and daily versions. (#1537) --- src/dotnet/devcontainer-feature.json | 4 +-- .../dotnet/install_dotnet_specific_release.sh | 6 ++--- test/dotnet/scenarios.json | 26 +------------------ 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/dotnet/devcontainer-feature.json b/src/dotnet/devcontainer-feature.json index 7d72c8cc0..7389b8398 100644 --- a/src/dotnet/devcontainer-feature.json +++ b/src/dotnet/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "dotnet", - "version": "2.4.1", + "version": "2.4.2", "name": "Dotnet CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/dotnet", "description": "This Feature installs the latest .NET SDK, which includes the .NET CLI and the shared runtime. Options are provided to choose a different version or additional versions.", @@ -12,8 +12,6 @@ "lts", "none", "10.0", - "10.0-preview", - "10.0-daily", "9.0", "8.0", "7.0", diff --git a/test/dotnet/install_dotnet_specific_release.sh b/test/dotnet/install_dotnet_specific_release.sh index 0d01bb4da..1ef587945 100644 --- a/test/dotnet/install_dotnet_specific_release.sh +++ b/test/dotnet/install_dotnet_specific_release.sh @@ -13,13 +13,13 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -expected=$(fetch_latest_version_in_channel "8.0") +expected=$(fetch_latest_version_in_channel "10.0") -check ".NET Core SDK 8.0 installed" \ +check ".NET Core SDK 10.0 installed" \ is_dotnet_sdk_version_installed "$expected" check "Build and run example project" \ -dotnet run --project projects/net8.0 +dotnet run --project projects/net10.0 # Report results # If any of the checks above exited with a non-zero exit code, the test will fail. diff --git a/test/dotnet/scenarios.json b/test/dotnet/scenarios.json index d7ae3acbb..62e2f1a46 100644 --- a/test/dotnet/scenarios.json +++ b/test/dotnet/scenarios.json @@ -13,7 +13,7 @@ "remoteUser": "vscode", "features": { "dotnet": { - "version": "8.0" + "version": "10.0" } } }, @@ -49,30 +49,6 @@ } } }, - "install_dotnet_preview": { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "remoteUser": "vscode", - "features": { - "dotnet": { - "version": "10.0-preview", - "additionalVersions": "10.0.1xx-preview", - "aspnetcoreRuntimeVersions": "10.0-preview", - "dotnetRuntimeVersions": "10.0-preview" - } - } - }, - "install_dotnet_daily": { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "remoteUser": "vscode", - "features": { - "dotnet": { - "version": "10.0-daily", - "additionalVersions": "10.0.1xx-daily", - "aspnetcoreRuntimeVersions": "10.0-daily", - "dotnetRuntimeVersions": "10.0-daily" - } - } - }, "install_dotnet_global_tool": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "remoteUser": "vscode", From 97fccabe1f86208940af5f133115729cf74046ca Mon Sep 17 00:00:00 2001 From: Kaniska Date: Mon, 19 Jan 2026 22:06:43 +0530 Subject: [PATCH 10/78] [docker-in-docker] - Fix for multiple vulnerability issues related to docker compose-switch component. (#1541) * [docker-in-docker] - Fix for multiple vulnerability issues related to docker compose-switch component. * Corrections in test scripts as per review comment. --- src/docker-in-docker/README.md | 2 +- .../devcontainer-feature.json | 4 ++-- src/docker-in-docker/install.sh | 2 +- test/docker-in-docker/docker_build.sh | 3 ++- .../docker_build_with_compose_switch.sh | 22 +++++++++++++++++++ test/docker-in-docker/docker_buildx.sh | 3 ++- test/docker-in-docker/docker_compose_v2.sh | 3 ++- .../docker_specific_moby_buildx.sh | 3 ++- test/docker-in-docker/scenarios.json | 12 ++++++++++ 9 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 test/docker-in-docker/docker_build_with_compose_switch.sh diff --git a/src/docker-in-docker/README.md b/src/docker-in-docker/README.md index 3fdbcc312..9c5370c4e 100644 --- a/src/docker-in-docker/README.md +++ b/src/docker-in-docker/README.md @@ -22,7 +22,7 @@ Create child containers *inside* a container, independent from the host's docker | azureDnsAutoDetection | Allow automatically setting the dockerd DNS server when the installation script detects it is running in Azure | boolean | true | | dockerDefaultAddressPool | Define default address pools for Docker networks. e.g. base=192.168.0.0/16,size=24 | string | - | | installDockerBuildx | Install Docker Buildx | boolean | true | -| installDockerComposeSwitch | Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter. | boolean | true | +| installDockerComposeSwitch | Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter. | boolean | false | | disableIp6tables | Disable ip6tables (this option is only applicable for Docker versions 27 and greater) | boolean | false | ## Customizations diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 33b0ad31c..56520a200 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": "2.13.0", + "version": "2.14.0", "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.", @@ -53,7 +53,7 @@ }, "installDockerComposeSwitch": { "type": "boolean", - "default": true, + "default": false, "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." }, "disableIp6tables": { diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index 10f3ef06c..3f30158e5 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -16,7 +16,7 @@ AZURE_DNS_AUTO_DETECTION="${AZUREDNSAUTODETECTION:-"true"}" DOCKER_DEFAULT_ADDRESS_POOL="${DOCKERDEFAULTADDRESSPOOL:-""}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" INSTALL_DOCKER_BUILDX="${INSTALLDOCKERBUILDX:-"true"}" -INSTALL_DOCKER_COMPOSE_SWITCH="${INSTALLDOCKERCOMPOSESWITCH:-"true"}" +INSTALL_DOCKER_COMPOSE_SWITCH="${INSTALLDOCKERCOMPOSESWITCH:-"false"}" MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" MICROSOFT_GPG_KEYS_ROLLING_URI="https://packages.microsoft.com/keys/microsoft-rolling.asc" DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal jammy noble" diff --git a/test/docker-in-docker/docker_build.sh b/test/docker-in-docker/docker_build.sh index 322a819fc..da5d31a3b 100755 --- a/test/docker-in-docker/docker_build.sh +++ b/test/docker-in-docker/docker_build.sh @@ -9,12 +9,13 @@ source dev-container-features-test-lib check "docker-buildx" docker buildx version check "docker-build" docker build ./ -check "installs compose-switch" bash -c "[[ -f /usr/local/bin/compose-switch ]]" check "docker compose" bash -c "docker compose version | grep -E '2.[0-9]+.[0-9]+'" check "docker-compose" bash -c "docker-compose --version | grep -E '2.[0-9]+.[0-9]+'" check "docker-buildx" bash -c "docker buildx version" check "docker-buildx-path" bash -c "ls -la /usr/libexec/docker/cli-plugins/docker-buildx" +check "Not installing compose-switch by default" bash -c "[[ ! -f /usr/local/bin/compose-switch ]]" + # Report result reportResults diff --git a/test/docker-in-docker/docker_build_with_compose_switch.sh b/test/docker-in-docker/docker_build_with_compose_switch.sh new file mode 100644 index 000000000..ff4f7b58f --- /dev/null +++ b/test/docker-in-docker/docker_build_with_compose_switch.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "docker-buildx" docker buildx version +check "docker-build" docker build ./ + +check "docker compose" bash -c "docker compose version | grep -E '2.[0-9]+.[0-9]+'" +check "docker-compose" bash -c "docker-compose --version | grep -E '2.[0-9]+.[0-9]+'" + +check "docker-buildx" bash -c "docker buildx version" +check "docker-buildx-path" bash -c "ls -la /usr/libexec/docker/cli-plugins/docker-buildx" + +check "installs compose-switch as the flag is on" bash -c "[[ -f /usr/local/bin/compose-switch ]]" + +# Report result +reportResults + diff --git a/test/docker-in-docker/docker_buildx.sh b/test/docker-in-docker/docker_buildx.sh index c571596fc..b66f7c57f 100755 --- a/test/docker-in-docker/docker_buildx.sh +++ b/test/docker-in-docker/docker_buildx.sh @@ -13,7 +13,8 @@ check "docker-buildx" docker buildx version check "docker-build" docker build ./ check "installs docker-compose v1 install" bash -c "type docker-compose" -check "installs compose-switch" bash -c "[[ -f /usr/local/bin/compose-switch ]]" + +check "Not installing compose-switch by default" bash -c "[[ ! -f /usr/local/bin/compose-switch ]]" # Report result reportResults diff --git a/test/docker-in-docker/docker_compose_v2.sh b/test/docker-in-docker/docker_compose_v2.sh index 5a512d2c5..854206cd1 100755 --- a/test/docker-in-docker/docker_compose_v2.sh +++ b/test/docker-in-docker/docker_compose_v2.sh @@ -10,7 +10,8 @@ source dev-container-features-test-lib check "docker compose" bash -c "docker compose version | grep -E '2.[0-9]+.[0-9]+'" check "docker-compose" bash -c "docker-compose --version | grep -E '2.[0-9]+.[0-9]+'" check "installs compose-switch as docker-compose" bash -c "[[ -f /usr/local/bin/docker-compose ]]" -check "installs compose-switch" bash -c "[[ -f /usr/local/bin/compose-switch ]]" + +check "Not installing compose-switch by default" bash -c "[[ ! -f /usr/local/bin/compose-switch ]]" # Report result reportResults diff --git a/test/docker-in-docker/docker_specific_moby_buildx.sh b/test/docker-in-docker/docker_specific_moby_buildx.sh index b2d4610a1..a6a7062d6 100755 --- a/test/docker-in-docker/docker_specific_moby_buildx.sh +++ b/test/docker-in-docker/docker_specific_moby_buildx.sh @@ -14,7 +14,8 @@ check "docker-buildx" docker buildx version check "docker-build" docker build ./ check "installs docker-compose v1 install" bash -c "type docker-compose" -check "installs compose-switch" bash -c "[[ -f /usr/local/bin/compose-switch ]]" + +check "Not installing compose-switch by default" bash -c "[[ ! -f /usr/local/bin/compose-switch ]]" # Report result reportResults diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index 334cb7798..baeaa6769 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -55,6 +55,18 @@ }, "remoteUser": "node" }, + "docker_build_with_compose_switch": { + "image": "mcr.microsoft.com/devcontainers/javascript-node:0-18", + "features": { + "docker-in-docker": { + "version": "latest", + "moby": "false", + "dockerDashComposeVersion": "v2", + "installDockerComposeSwitch": true + } + }, + "remoteUser": "node" + }, "docker_build_2": { "image": "ubuntu:noble", "features": { From 7cc36aa71f16fa1b65720b310696f5d1b8084449 Mon Sep 17 00:00:00 2001 From: Eimhin Laverty Date: Fri, 23 Jan 2026 14:59:49 +0100 Subject: [PATCH 11/78] Provide error response when failure occurs fetching Git tags (#1414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Provide error response when failure occurs fetching Git tags * bump version * Use alternative approach due to --fail-with-body only being available on newer curl releases --------- Co-authored-by: รlvaro Rausell Guiard <33221237+AlvaroRausell@users.noreply.github.com> --- src/git/devcontainer-feature.json | 2 +- src/git/install.sh | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/git/devcontainer-feature.json b/src/git/devcontainer-feature.json index 7531ad2ab..5aa83a8b1 100644 --- a/src/git/devcontainer-feature.json +++ b/src/git/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "git", - "version": "1.3.4", + "version": "1.3.5", "name": "Git (from source)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/git", "description": "Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.", diff --git a/src/git/install.sh b/src/git/install.sh index 4124fc80c..7ee301b7e 100755 --- a/src/git/install.sh +++ b/src/git/install.sh @@ -288,7 +288,16 @@ fi # Partial version matching if [ "$(echo "${GIT_VERSION}" | grep -o '\.' | wc -l)" != "2" ]; then requested_version="${GIT_VERSION}" - version_list="$(curl -sSL -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/git/git/tags" | grep -oP '"name":\s*"v\K[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -rV )" + response_output_file=$(mktemp) + trap 'rm "$response_output_file"' EXIT + + http_code=$(curl --silent --output $response_output_file --write-out "%{http_code}" -sSL -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/git/git/tags") + version_content=$(cat "$response_output_file") + if [[ ${http_code} -lt 200 || ${http_code} -gt 299 ]] ; then + echo "$version_content" >&2 + exit 1 + fi + version_list="$(echo "$version_content" | grep -oP '"name":\s*"v\K[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -rV )" if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "current" ]; then GIT_VERSION="$(echo "${version_list}" | head -n 1)" else From a5448c05e1a341c6d90dec5ea8ffb5626e620b82 Mon Sep 17 00:00:00 2001 From: Paul Taylor <178183+trxcllnt@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:52:30 -0800 Subject: [PATCH 12/78] Bump common-utils version (#1524) bump common-utils version --- src/common-utils/devcontainer-feature.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 874093427..14056e3a9 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.5.5", + "version": "2.5.6", "name": "Common Utilities", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/common-utils", "description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.", From 7747ccf6b90cad974b394d31ae7041f35332875e Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 27 Jan 2026 21:40:18 +0530 Subject: [PATCH 13/78] [node] - Fixing issue due to expired GPG key for yarn. (#1546) * [node] - Fixing issue due to expised GPG key for yarn. * Changing back to debian 11 * Fixing failing tests * Trigger test again --- src/node/devcontainer-feature.json | 4 ++-- src/node/install.sh | 11 +++++++++-- test/node/install_additional_node.sh | 8 ++++---- .../install_additional_node_on_rhel_family.sh | 7 +++---- test/node/install_node_debian_bookworm.sh | 17 +++++++++++++++++ test/node/install_node_debian_trixie.sh | 17 +++++++++++++++++ test/node/scenarios.json | 16 ++++++++++++++++ 7 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 test/node/install_node_debian_bookworm.sh create mode 100644 test/node/install_node_debian_trixie.sh diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 1828ba0d5..923afd2c2 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,7 +1,7 @@ { "id": "node", - "version": "1.6.3", - "name": "Node.js (via nvm), yarn and pnpm", + "version": "1.6.4", + "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.", "options": { diff --git a/src/node/install.sh b/src/node/install.sh index 46d448440..71d91ffe2 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -203,8 +203,15 @@ install_yarn() { # via apt-get on Debian systems if ! type yarn >/dev/null 2>&1; then # Import key safely (new method rather than deprecated apt-key approach) and install - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list + if [ "${VERSION_CODENAME}" = "trixie" ]; then + # Trixie requires fetching the key from keys.openpgp.org + mkdir -p /etc/apt/keyrings + curl -fsSL "https://keys.openpgp.org/vks/v1/by-fingerprint/72ECF46A56B4AD39C907BBB71646B01B86E50310" | gpg --dearmor --yes -o /etc/apt/keyrings/yarn-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list + else + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list + fi apt-get update apt-get -y install --no-install-recommends yarn else diff --git a/test/node/install_additional_node.sh b/test/node/install_additional_node.sh index 527dacf25..4ef5012d3 100755 --- a/test/node/install_additional_node.sh +++ b/test/node/install_additional_node.sh @@ -6,12 +6,12 @@ set -e source dev-container-features-test-lib # 'lts' is now some version of node 22... -check "version_on_path" node -v | grep 22 +check "version_on_path" bash -c "node -v | grep 24" check "pnpm" bash -c "pnpm -v | grep 8.8.0" -check "v20_installed" ls -1 /usr/local/share/nvm/versions/node | grep 20.19.1 -check "v19_installed" ls -1 /usr/local/share/nvm/versions/node | grep 19.9.0 +check "v20_installed" bash -c "ls -1 /usr/local/share/nvm/versions/node | grep 20.19.1" +check "v19_installed" bash -c "ls -1 /usr/local/share/nvm/versions/node | grep 19.9.0" # Report result -reportResults +reportResults \ No newline at end of file diff --git a/test/node/install_additional_node_on_rhel_family.sh b/test/node/install_additional_node_on_rhel_family.sh index a10ca82f4..3e3a75777 100644 --- a/test/node/install_additional_node_on_rhel_family.sh +++ b/test/node/install_additional_node_on_rhel_family.sh @@ -6,12 +6,11 @@ set -e source dev-container-features-test-lib # 'lts' is now some version of node 22... -check "version_on_path" node -v | grep 22 +check "version_on_path" bash -c "node -v | grep 24" check "pnpm" bash -c "pnpm -v | grep 6.16.0" -check "v20_installed" ls -1 /usr/local/share/nvm/versions/node | grep 20 -check "v19_installed" ls -1 /usr/local/share/nvm/versions/node | grep 19.9.0 -check "v20_installed" ls -1 /usr/local/share/nvm/versions/node | grep 20.19.1 +check "v19_installed" bash -c "ls -1 /usr/local/share/nvm/versions/node | grep 19.9.0" +check "v20_installed" bash -c "ls -1 /usr/local/share/nvm/versions/node | grep 20.19.1" # Report result diff --git a/test/node/install_node_debian_bookworm.sh b/test/node/install_node_debian_bookworm.sh new file mode 100644 index 000000000..53b29fcaa --- /dev/null +++ b/test/node/install_node_debian_bookworm.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "version" node --version +check "pnpm" pnpm -v +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "yarn" yarn --version + +# Report result +reportResults + + diff --git a/test/node/install_node_debian_trixie.sh b/test/node/install_node_debian_trixie.sh new file mode 100644 index 000000000..53b29fcaa --- /dev/null +++ b/test/node/install_node_debian_trixie.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "version" node --version +check "pnpm" pnpm -v +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "yarn" yarn --version + +# Report result +reportResults + + diff --git a/test/node/scenarios.json b/test/node/scenarios.json index d9a0c527e..d4c5dcb50 100644 --- a/test/node/scenarios.json +++ b/test/node/scenarios.json @@ -1,4 +1,20 @@ { + "install_node_debian_trixie": { + "image": "debian:13", + "features": { + "node": { + "version": "lts" + } + } + }, + "install_node_debian_bookworm": { + "image": "debian:12", + "features": { + "node": { + "version": "lts" + } + } + }, "nvm_test_fallback": { "image": "debian:11", "features": { From 2d57b3c3ef94e8a037230abc6433c84a26209168 Mon Sep 17 00:00:00 2001 From: Emilien Escalle Date: Thu, 29 Jan 2026 17:41:42 +0100 Subject: [PATCH 14/78] feat(github-cli): add support for extensions (#1530) Signed-off-by: Emilien Escalle Signed-off-by: Emilien Escalle --- src/github-cli/NOTES.md | 6 +- src/github-cli/README.md | 15 ++- src/github-cli/devcontainer-feature.json | 10 +- src/github-cli/install.sh | 34 +++++++ src/github-cli/scripts/install-extensions.sh | 97 ++++++++++++++++++++ test/github-cli/install_extensions.sh | 14 +++ test/github-cli/scenarios.json | 27 ++++-- 7 files changed, 180 insertions(+), 23 deletions(-) create mode 100644 src/github-cli/scripts/install-extensions.sh create mode 100644 test/github-cli/install_extensions.sh diff --git a/src/github-cli/NOTES.md b/src/github-cli/NOTES.md index 19fe92f31..e742805e6 100644 --- a/src/github-cli/NOTES.md +++ b/src/github-cli/NOTES.md @@ -1,7 +1,9 @@ - - ## OS Support This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. `bash` is required to execute the `install.sh` script. + +## Extensions + +If you set the `extensions` option, the feature will run `gh extension install` for each entry (comma-separated). Extensions are installed for the most appropriate non-root user (based on `USERNAME` / `_REMOTE_USER`), with a fallback to `root`. diff --git a/src/github-cli/README.md b/src/github-cli/README.md index 07945081a..0da722f69 100644 --- a/src/github-cli/README.md +++ b/src/github-cli/README.md @@ -1,4 +1,3 @@ - # GitHub CLI (github-cli) Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies. @@ -13,12 +12,11 @@ Installs the GitHub CLI. Auto-detects latest version and installs needed depende ## Options -| Options Id | Description | Type | Default Value | -|-----|-----|-----|-----| -| version | Select version of the GitHub CLI, if not latest. | string | latest | -| installDirectlyFromGitHubRelease | - | boolean | true | - - +| Options Id | Description | Type | Default Value | +| -------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | ------------- | +| version | Select version of the GitHub CLI, if not latest. | string | latest | +| installDirectlyFromGitHubRelease | - | boolean | true | +| extensions | Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot'). | string | | ## OS Support @@ -26,7 +24,6 @@ This Feature should work on recent versions of Debian/Ubuntu-based distributions `bash` is required to execute the `install.sh` script. - --- -_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/github-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/github-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/github-cli/devcontainer-feature.json b/src/github-cli/devcontainer-feature.json index b3eca81f0..15a91e43d 100644 --- a/src/github-cli/devcontainer-feature.json +++ b/src/github-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "github-cli", - "version": "1.0.15", + "version": "1.1.0", "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.", @@ -17,6 +17,11 @@ "installDirectlyFromGitHubRelease": { "type": "boolean", "default": true + }, + "extensions": { + "type": "string", + "default": "", + "description": "Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot')." } }, "customizations": { @@ -34,5 +39,4 @@ "ghcr.io/devcontainers/features/common-utils", "ghcr.io/devcontainers/features/git" ] -} - +} \ No newline at end of file diff --git a/src/github-cli/install.sh b/src/github-cli/install.sh index 11af21d08..e3eaba0c3 100755 --- a/src/github-cli/install.sh +++ b/src/github-cli/install.sh @@ -9,6 +9,7 @@ CLI_VERSION=${VERSION:-"latest"} INSTALL_DIRECTLY_FROM_GITHUB_RELEASE=${INSTALLDIRECTLYFROMGITHUBRELEASE:-"true"} +EXTENSIONS=${EXTENSIONS:-""} GITHUB_CLI_ARCHIVE_GPG_KEY=23F3D4EA75716059 @@ -242,5 +243,38 @@ else echo "Done!" fi +# Install requested GitHub CLI extensions (if any) +if [ -n "${EXTENSIONS}" ]; then + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + EXTENSIONS_SCRIPT="${SCRIPT_DIR}/scripts/install-extensions.sh" + + # Determine the appropriate non-root user (mirrors other features' "automatic" behavior) + USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" + if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if [ -n "${CURRENT_USER}" ] && id -u "${CURRENT_USER}" > /dev/null 2>&1; then + USERNAME="${CURRENT_USER}" + break + fi + done + if [ -z "${USERNAME}" ]; then + USERNAME=root + fi + elif [ "${USERNAME}" = "none" ] || ! id -u "${USERNAME}" > /dev/null 2>&1; then + USERNAME=root + fi + + if [ "${USERNAME}" = "root" ]; then + EXTENSIONS="${EXTENSIONS}" bash "${EXTENSIONS_SCRIPT}" + else + EXTENSIONS_ESCAPED="$(printf '%q' "${EXTENSIONS}")" + USERNAME_ESCAPED="$(printf '%q' "${USERNAME}")" + su - "${USERNAME}" -c "EXTENSIONS=${EXTENSIONS_ESCAPED} USERNAME=${USERNAME_ESCAPED} INSTALL_EXTENSIONS=true bash '${EXTENSIONS_SCRIPT}'" + INSTALL_EXTENSIONS=false bash "${EXTENSIONS_SCRIPT}" + fi +fi + # Clean up rm -rf /var/lib/apt/lists/* diff --git a/src/github-cli/scripts/install-extensions.sh b/src/github-cli/scripts/install-extensions.sh new file mode 100644 index 000000000..436accf03 --- /dev/null +++ b/src/github-cli/scripts/install-extensions.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +set -e + +EXTENSIONS=${EXTENSIONS:-""} +INSTALL_EXTENSIONS=${INSTALL_EXTENSIONS:-"true"} + +trim() { + local value="$1" + value="${value#${value%%[![:space:]]*}}" + value="${value%${value##*[![:space:]]}}" + echo "${value}" +} + +install_extension() { + local extension="$1" + local extensions_root + local repo_name + + extensions_root="${XDG_DATA_HOME:-"${HOME}/.local/share"}/gh/extensions" + repo_name="${extension##*/}" + + mkdir -p "${extensions_root}" + if [ ! -d "${extensions_root}/${repo_name}" ]; then + git clone --depth 1 "https://github.com/${extension}.git" "${extensions_root}/${repo_name}" + fi +} + +ensure_gh_extension_list_wrapper() { + if [ "$(id -u)" -ne 0 ]; then + return + fi + + if gh extension list >/dev/null 2>&1; then + return + fi + + cat > /usr/local/bin/gh <<'EOF' +#!/usr/bin/env bash +set -e + +REAL_GH=/usr/bin/gh + +if [ "$#" -ge 2 ]; then + cmd="$1" + sub="$2" + if { [ "$cmd" = "extension" ] || [ "$cmd" = "extensions" ] || [ "$cmd" = "ext" ]; } && { [ "$sub" = "list" ] || [ "$sub" = "ls" ]; }; then + extensions_root="${XDG_DATA_HOME:-"$HOME/.local/share"}/gh/extensions" + if [ -d "$extensions_root" ]; then + shopt -s nullglob + for d in "$extensions_root"/*; do + [ -d "$d" ] || continue + url="" + if command -v git >/dev/null 2>&1 && [ -d "$d/.git" ]; then + url="$(git -C "$d" config --get remote.origin.url 2>/dev/null || true)" + fi + if [ -n "$url" ]; then + url="${url%.git}" + url="${url#https://github.com/}" + url="${url#http://github.com/}" + url="${url#ssh://git@github.com/}" + url="${url#git@github.com:}" + echo "$url" + fi + done + fi + exit 0 + fi +fi + +exec "$REAL_GH" "$@" +EOF + chmod +x /usr/local/bin/gh +} + +if [ "${INSTALL_EXTENSIONS}" = "true" ]; then + if [ -z "${EXTENSIONS}" ]; then + exit 0 + fi + + echo "Installing GitHub CLI extensions: ${EXTENSIONS}" + IFS=',' read -r -a extension_list <<< "${EXTENSIONS}" + for extension in "${extension_list[@]}"; do + extension="$(trim "${extension}")" + if [ -z "${extension}" ]; then + continue + fi + + install_extension "${extension}" + done +fi + +ensure_gh_extension_list_wrapper diff --git a/test/github-cli/install_extensions.sh b/test/github-cli/install_extensions.sh new file mode 100644 index 000000000..78cb126f9 --- /dev/null +++ b/test/github-cli/install_extensions.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +check "gh-version" gh --version + +check "gh-extension-installed" gh extension list | grep -q 'dlvhdr/gh-dash' +check "gh-extension-installed-2" gh extension list | grep -q 'github/gh-copilot' + +# Report result +reportResults diff --git a/test/github-cli/scenarios.json b/test/github-cli/scenarios.json index ea6eb09d1..eafee3c59 100644 --- a/test/github-cli/scenarios.json +++ b/test/github-cli/scenarios.json @@ -1,11 +1,20 @@ { - "install_git_cli_from_release": { - "image": "ubuntu:noble", - "features": { - "github-cli": { - "version": "latest", - "installDirectlyFromGitHubRelease": "false" - } - } + "install_git_cli_from_release": { + "image": "ubuntu:noble", + "features": { + "github-cli": { + "version": "latest", + "installDirectlyFromGitHubRelease": "false" + } } -} \ No newline at end of file + }, + "install_extensions": { + "image": "ubuntu:noble", + "features": { + "github-cli": { + "version": "latest", + "extensions": "dlvhdr/gh-dash,github/gh-copilot" + } + } + } +} From 362dfda2f51d24e8e3bb134aef371a1aae76e703 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Thu, 29 Jan 2026 23:06:52 +0530 Subject: [PATCH 15/78] [node] - Removal of default installation of yarn v1(classic) (#1550) * [node] - Removal of default installation of yarm v1(classic) * Fixing issue when node version passed as none. * check yarn version as well. * Minor version change as per review comment. --- src/node/README.md | 2 +- src/node/devcontainer-feature.json | 4 ++-- src/node/install.sh | 6 ++++-- test/node/install_node_debian_bookworm.sh | 15 +++++++++++++-- test/node/install_node_debian_trixie.sh | 15 ++++++++++++++- test/node/scenarios.json | 8 -------- 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/node/README.md b/src/node/README.md index 328af529f..2c6f8cba6 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -20,7 +20,7 @@ Installs Node.js, nvm, yarn, pnpm, and needed dependencies. | nvmInstallPath | The path where NVM will be installed. | string | /usr/local/share/nvm | | pnpmVersion | Select or enter the PNPM version to install | string | latest | | nvmVersion | Version of NVM to install. | string | latest | -| installYarnUsingApt | On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs. | boolean | true | +| installYarnUsingApt | On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs. | boolean | false | ## Customizations diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 923afd2c2..ce792e43d 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "1.6.4", + "version": "1.7.0", "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.", @@ -52,7 +52,7 @@ }, "installYarnUsingApt": { "type": "boolean", - "default": true, + "default": false, "description": "On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs." } }, diff --git a/src/node/install.sh b/src/node/install.sh index 71d91ffe2..81696d59b 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -12,7 +12,7 @@ export PNPM_VERSION="${PNPMVERSION:-"latest"}" export NVM_VERSION="${NVMVERSION:-"latest"}" export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}" INSTALL_TOOLS_FOR_NODE_GYP="${NODEGYPDEPENDENCIES:-true}" -export INSTALL_YARN_USING_APT="${INSTALLYARNUSINGAPT:-true}" # only concerns Debian-based systems +export INSTALL_YARN_USING_APT="${INSTALLYARNUSINGAPT:-false}" # only concerns Debian-based systems # Comma-separated list of node versions to be installed (with nvm) # alongside NODE_VERSION, but not set as default. @@ -362,7 +362,9 @@ else fi # Possibly install yarn (puts yarn in per-Node install on RHEL, uses system yarn on Debian) -install_yarn +if [ -n "${NODE_VERSION}" ] && [ "${NODE_VERSION}" != "none" ]; then + install_yarn +fi # Additional node versions to be installed but not be set as # default we can assume the nvm is the group owner of the nvm diff --git a/test/node/install_node_debian_bookworm.sh b/test/node/install_node_debian_bookworm.sh index 53b29fcaa..8fb7230f5 100644 --- a/test/node/install_node_debian_bookworm.sh +++ b/test/node/install_node_debian_bookworm.sh @@ -5,13 +5,24 @@ set -e # Optional: Import test library source dev-container-features-test-lib +# Definition specific tests +YARN_VERSION="4.9.4" + # Definition specific tests check "version" node --version check "pnpm" pnpm -v -check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" check "yarn" yarn --version +# Corepack provides shims for package managers like yarn. The first time yarn is invoked via the "yarn" +# command, corepack will interactively request permission to download the yarn binary. To +# avoid this interactive mode and download the binary automatically, we explicitly call "corepack use yarn" +# instead (doesn't require user input). Once that command completes, "yarn" can be used in a non-interactive mode. +check "yarn shim location" bash -c ". /usr/local/share/nvm/nvm.sh && type yarn &> /dev/null" +check "download yarn" bash -c ". /usr/local/share/nvm/nvm.sh && corepack use yarn@${YARN_VERSION}" +check "yarn version" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version | grep ${YARN_VERSION}" + +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" + # Report result reportResults - diff --git a/test/node/install_node_debian_trixie.sh b/test/node/install_node_debian_trixie.sh index 53b29fcaa..7f92b561d 100644 --- a/test/node/install_node_debian_trixie.sh +++ b/test/node/install_node_debian_trixie.sh @@ -5,12 +5,25 @@ set -e # Optional: Import test library source dev-container-features-test-lib +# Definition specific tests +YARN_VERSION="4.9.4" + # Definition specific tests check "version" node --version check "pnpm" pnpm -v -check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" check "yarn" yarn --version +# Corepack provides shims for package managers like yarn. The first time yarn is invoked via the "yarn" +# command, corepack will interactively request permission to download the yarn binary. To +# avoid this interactive mode and download the binary automatically, we explicitly call "corepack use yarn" +# instead (doesn't require user input). Once that command completes, "yarn" can be used in a non-interactive mode. + +check "yarn shim location" bash -c ". /usr/local/share/nvm/nvm.sh && type yarn &> /dev/null" +check "download yarn" bash -c ". /usr/local/share/nvm/nvm.sh && corepack use yarn@${YARN_VERSION}" +check "yarn version" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version | grep ${YARN_VERSION}" + +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" + # Report result reportResults diff --git a/test/node/scenarios.json b/test/node/scenarios.json index d4c5dcb50..e3b4297a1 100644 --- a/test/node/scenarios.json +++ b/test/node/scenarios.json @@ -207,13 +207,5 @@ "version": "lts" } } - }, - "debian_yarn_from_corepack": { - "image": "debian:11", - "features": { - "node": { - "installYarnUsingApt": false - } - } } } From c85af4dc9393926c805acf05a0c4eb5818b72777 Mon Sep 17 00:00:00 2001 From: FreddielyFire Date: Thu, 29 Jan 2026 15:34:17 -0800 Subject: [PATCH 16/78] fix(node): Use dl.yarnpkg.com for Yarn GPG key on all Debian versions (#1547) * fix(node): Use dl.yarnpkg.com for Yarn GPG key on all Debian versions Branch-Creation-Time: 2026-01-29T00:16:22+0000 * bump version to 1.6.5 --------- Co-authored-by: llin2 Co-authored-by: Abdurrahmaan Iqbal <137001048+abdurriq@users.noreply.github.com> --- src/node/devcontainer-feature.json | 4 ++-- src/node/install.sh | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index ce792e43d..c8ceb966f 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "1.7.0", + "version": "1.7.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.", @@ -78,4 +78,4 @@ "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" ] -} \ No newline at end of file +} diff --git a/src/node/install.sh b/src/node/install.sh index 81696d59b..1d89abd0a 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -203,15 +203,9 @@ install_yarn() { # via apt-get on Debian systems if ! type yarn >/dev/null 2>&1; then # Import key safely (new method rather than deprecated apt-key approach) and install - if [ "${VERSION_CODENAME}" = "trixie" ]; then - # Trixie requires fetching the key from keys.openpgp.org - mkdir -p /etc/apt/keyrings - curl -fsSL "https://keys.openpgp.org/vks/v1/by-fingerprint/72ECF46A56B4AD39C907BBB71646B01B86E50310" | gpg --dearmor --yes -o /etc/apt/keyrings/yarn-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list - else - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list - fi + mkdir -p /etc/apt/keyrings + curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor --yes -o /etc/apt/keyrings/yarn-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list apt-get update apt-get -y install --no-install-recommends yarn else From 9735099e4c9c7db64d59f518654553a7e00f96ab Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 10 Feb 2026 22:12:26 +0530 Subject: [PATCH 17/78] [powershell] - Add `lts`, `stable` and `preview`, also remove deprecated versions. (#1562) * [powershell] - Add `lts` and remove deprecated versions. * re-triggger test * Re-trigger the test. * Support powershell installation in debian trixie(13) * Supporting `lts`, `preview` and `stable`. * Remove extra dot(.) * Retrigger test * Added tests for almalinux * Added one more test * Remove duplicate code comment --- src/powershell/README.md | 2 +- src/powershell/devcontainer-feature.json | 12 +- src/powershell/install.sh | 125 ++++++++++++++++-- test/powershell/install_modules.sh | 3 + .../install_powershell_fallback_test.sh | 4 +- .../powershell_lts_version_almalinux.sh | 14 ++ .../powershell_lts_version_debian.sh | 14 ++ test/powershell/powershell_preview_version.sh | 14 ++ .../powershell_preview_version_almalinux.sh | 14 ++ .../powershell_preview_version_debian.sh | 14 ++ test/powershell/powershell_stable_version.sh | 14 ++ .../powershell_stable_version_almalinux.sh | 14 ++ .../powershell_stable_version_debian.sh | 14 ++ test/powershell/scenarios.json | 79 +++++++++++ ...validate_powershell_installation_debian.sh | 20 +++ .../validate_powershell_installation_spec.sh | 20 +++ 16 files changed, 360 insertions(+), 17 deletions(-) create mode 100755 test/powershell/powershell_lts_version_almalinux.sh create mode 100755 test/powershell/powershell_lts_version_debian.sh create mode 100755 test/powershell/powershell_preview_version.sh create mode 100755 test/powershell/powershell_preview_version_almalinux.sh create mode 100755 test/powershell/powershell_preview_version_debian.sh create mode 100755 test/powershell/powershell_stable_version.sh create mode 100755 test/powershell/powershell_stable_version_almalinux.sh create mode 100755 test/powershell/powershell_stable_version_debian.sh create mode 100644 test/powershell/validate_powershell_installation_debian.sh create mode 100644 test/powershell/validate_powershell_installation_spec.sh 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 + From 29e3e861989b70b121ac6f32c89f68487ce6b5ea Mon Sep 17 00:00:00 2001 From: Kaniska Date: Thu, 19 Feb 2026 19:51:37 +0530 Subject: [PATCH 18/78] [powershell] - Fixing installation issue on `arm64` (#1581) * [powershell] - Fixing installation issue on `arm64` * Adding new workflow for arm64 on powershell feature * add wget if not present. --- .github/workflows/test-pr-arm64.yaml | 67 ++++++++++++++++++++++++ src/powershell/devcontainer-feature.json | 2 +- src/powershell/install.sh | 6 +-- 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/test-pr-arm64.yaml diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml new file mode 100644 index 000000000..d82ec3409 --- /dev/null +++ b/.github/workflows/test-pr-arm64.yaml @@ -0,0 +1,67 @@ +name: "PR - Test Updated Features (arm64)" +on: + pull_request: + # NOTE: To extend this workflow to other features, add path entries below + # following the same pattern, e.g.: + # - "src//**" + # - "test//**" + paths: + - "src/powershell/**" + - "test/powershell/**" + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + features: ${{ steps.filter.outputs.changes }} + steps: + - uses: dorny/paths-filter@v3 + id: filter + with: + # NOTE: To extend this workflow to other features, add filter entries below + # following the same pattern, e.g.: + # : ./**//** + filters: | + powershell: ./**/powershell/** + + test: + needs: [detect-changes] + runs-on: ubuntu-24.04-arm + continue-on-error: true + strategy: + matrix: + features: ${{ fromJSON(needs.detect-changes.outputs.features) }} + baseImage: + [ + "ubuntu:focal", + "ubuntu:jammy", + "debian:11", + "debian:12", + "mcr.microsoft.com/devcontainers/base:ubuntu", + "mcr.microsoft.com/devcontainers/base:debian", + "mcr.microsoft.com/devcontainers/base:noble" + ] + steps: + - uses: actions/checkout@v4 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Generating tests for '${{ matrix.features }}' against '${{ matrix.baseImage }}'" + run: devcontainer features test --skip-scenarios -f ${{ matrix.features }} -i ${{ matrix.baseImage }} . + + test-scenarios: + needs: [detect-changes] + runs-on: ubuntu-24.04-arm + continue-on-error: true + strategy: + matrix: + features: ${{ fromJSON(needs.detect-changes.outputs.features) }} + steps: + - uses: actions/checkout@v4 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Testing '${{ matrix.features }}' scenarios" + run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . diff --git a/src/powershell/devcontainer-feature.json b/src/powershell/devcontainer-feature.json index 2f511b212..3b500608d 100644 --- a/src/powershell/devcontainer-feature.json +++ b/src/powershell/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "powershell", - "version": "2.0.0", + "version": "2.0.1", "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 c12b72b75..40308c32e 100755 --- a/src/powershell/install.sh +++ b/src/powershell/install.sh @@ -367,7 +367,7 @@ install_using_github() { # Fall back on direct download if no apt package exists in microsoft pool 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] + check_packages curl ca-certificates gnupg2 dirmngr libc6 libgcc1 libgssapi-krb5-2 libstdc++6 libunwind8 libuuid1 zlib1g libicu[0-9][0-9] wget 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 @@ -394,10 +394,10 @@ install_using_github() { install_prev_pwsh $pwsh_url fi - # downlaod the latest version of powershell and extracting the file to powershell directory + # download the latest version of powershell and extracting the file to powershell directory wget https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/${powershell_filename} mkdir ~/powershell - tar -xvf powershell-${POWERSHELL_VERSION}-linux-x64.tar.gz -C ~/powershell + tar -xvf ${powershell_filename} -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 From 4ed1c6d723ae3818e30d7abfbfc06b471ad9f53c Mon Sep 17 00:00:00 2001 From: Abdurrahmaan Iqbal Date: Thu, 19 Feb 2026 16:24:42 +0000 Subject: [PATCH 19/78] Fix conda installation failure due to SHA1 signature rejection (#1576) Fix conda installation failure due to SHA1 signature rejection (#1565) * Initial plan * Fix conda installation by switching from apt repository to direct Miniconda installer * Add error handling for Miniconda download and installation * Use APT::Key::GPGVCommand=1 option to bypass SHA1 signature check * Fix version to 1.2.1 for semantic versioning * Revert to Miniconda installer approach - APT option caused gpgv errors * Use mktemp for secure temporary file creation * Replace Miniconda installer with direct .deb package download and apt install * Fix apt-get install syntax and improve error handling * Fix package filename - use architecture-specific deb and extract Filename from Packages * Update version to 1.2.3 * Fix apt-get install path - remove ./ prefix for absolute paths * Fix version matching for specific conda versions - handle version suffixes --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska --- src/conda/devcontainer-feature.json | 80 ++++++++++++++--------------- src/conda/install.sh | 76 +++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 49 deletions(-) diff --git a/src/conda/devcontainer-feature.json b/src/conda/devcontainer-feature.json index 163696a20..cd590f9b7 100644 --- a/src/conda/devcontainer-feature.json +++ b/src/conda/devcontainer-feature.json @@ -1,43 +1,43 @@ { - "id": "conda", - "version": "1.0.10", - "name": "Conda", - "description": "A cross-platform, language-agnostic binary package manager", - "documentationURL": "https://github.com/devcontainers/features/tree/main/src/conda", - "options": { - "version": { - "type": "string", - "proposals": [ - "latest", - "4.11.0", - "4.12.0" - ], - "default": "latest", - "description": "Select or enter a conda version." - }, - "addCondaForge": { - "type": "boolean", - "default": false, - "description": "Add conda-forge channel to the config?" - } + "id": "conda", + "version": "1.2.5", + "name": "Conda", + "description": "A cross-platform, language-agnostic binary package manager", + "documentationURL": "https://github.com/devcontainers/features/tree/main/src/conda", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "4.11.0", + "4.12.0" + ], + "default": "latest", + "description": "Select or enter a conda version." }, - "containerEnv": { - "CONDA_DIR": "/opt/conda", - "CONDA_SCRIPT":"/opt/conda/etc/profile.d/conda.sh", - "PATH": "/opt/conda/bin:${PATH}" - }, - "customizations": { - "vscode": { - "settings": { - "github.copilot.chat.codeGeneration.instructions": [ - { - "text": "This dev container includes the conda package manager pre-installed and available on the `PATH` for data science and Python development. Additional packages installed using Conda will be downloaded from Anaconda or another repository configured by the user. A user can install different versions of Python than the one in this dev container by running a command like: conda install python=3.7" - } - ] - } - } - }, - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" - ] + "addCondaForge": { + "type": "boolean", + "default": false, + "description": "Add conda-forge channel to the config?" + } + }, + "containerEnv": { + "CONDA_DIR": "/opt/conda", + "CONDA_SCRIPT": "/opt/conda/etc/profile.d/conda.sh", + "PATH": "/opt/conda/bin:${PATH}" + }, + "customizations": { + "vscode": { + "settings": { + "github.copilot.chat.codeGeneration.instructions": [ + { + "text": "This dev container includes the conda package manager pre-installed and available on the `PATH` for data science and Python development. Additional packages installed using Conda will be downloaded from Anaconda or another repository configured by the user. A user can install different versions of Python than the one in this dev container by running a command like: conda install python=3.7" + } + ] + } + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] } diff --git a/src/conda/install.sh b/src/conda/install.sh index 43ab82f54..73925dd5d 100644 --- a/src/conda/install.sh +++ b/src/conda/install.sh @@ -83,20 +83,78 @@ if ! conda --version &> /dev/null ; then usermod -a -G conda "${USERNAME}" # Install dependencies - check_packages curl ca-certificates gnupg2 + check_packages curl ca-certificates echo "Installing Conda..." - curl -sS https://repo.anaconda.com/pkgs/misc/gpgkeys/anaconda.asc | gpg --dearmor > /usr/share/keyrings/conda-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/conda-archive-keyring.gpg] https://repo.anaconda.com/pkgs/misc/debrepo/conda stable main" > /etc/apt/sources.list.d/conda.list - apt-get update -y - - CONDA_PKG="conda=${VERSION}-0" + # Download .deb package directly from repository (bypassing SHA1 signature issue) + TEMP_DEB="$(mktemp -t conda_XXXXXX.deb)" + CONDA_REPO_BASE="https://repo.anaconda.com/pkgs/misc/debrepo/conda" + + # Determine package filename based on requested version + ARCH="$(dpkg --print-architecture 2>/dev/null || echo "amd64")" + PACKAGES_URL="https://repo.anaconda.com/pkgs/misc/debrepo/conda/dists/stable/main/binary-${ARCH}/Packages" + if [ "${VERSION}" = "latest" ]; then - CONDA_PKG="conda" + # For latest, we need to query the repository to find the current version + echo "Fetching package list to determine latest version..." + CONDA_PKG_INFO=$(curl -fsSL "${PACKAGES_URL}" | grep -A 30 "^Package: conda$" | head -n 31) + CONDA_VERSION=$(echo "${CONDA_PKG_INFO}" | grep "^Version:" | head -n 1 | awk '{print $2}') + CONDA_FILENAME=$(echo "${CONDA_PKG_INFO}" | grep "^Filename:" | head -n 1 | awk '{print $2}') + + if [ -z "${CONDA_VERSION}" ] || [ -z "${CONDA_FILENAME}" ]; then + echo "ERROR: Could not determine latest conda version or filename from ${PACKAGES_URL}" + echo "This may indicate an unsupported architecture or repository unavailability." + rm -f "${TEMP_DEB}" + exit 1 + fi + + CONDA_PKG_NAME="${CONDA_FILENAME}" + else + # For specific versions, query the Packages file to find the exact filename + echo "Fetching package list to find version ${VERSION}..." + # Search for version pattern - user may specify 4.12.0 but package has 4.12.0-0 + CONDA_PKG_INFO=$(curl -fsSL "${PACKAGES_URL}" | grep -A 30 "^Package: conda$" | grep -B 5 -A 25 "^Version: ${VERSION}") + CONDA_FILENAME=$(echo "${CONDA_PKG_INFO}" | grep "^Filename:" | head -n 1 | awk '{print $2}') + + if [ -z "${CONDA_FILENAME}" ]; then + echo "ERROR: Could not find conda version ${VERSION} in ${PACKAGES_URL}" + echo "Please verify the version specified is valid." + rm -f "${TEMP_DEB}" + exit 1 + fi + + CONDA_PKG_NAME="${CONDA_FILENAME}" fi - - check_packages $CONDA_PKG + + # Download the .deb package + CONDA_DEB_URL="${CONDA_REPO_BASE}/${CONDA_PKG_NAME}" + echo "Downloading conda package from ${CONDA_DEB_URL}..." + + if ! curl -fsSL "${CONDA_DEB_URL}" -o "${TEMP_DEB}"; then + echo "ERROR: Failed to download conda .deb package from ${CONDA_DEB_URL}" + echo "Please verify the version specified is valid." + rm -f "${TEMP_DEB}" + exit 1 + fi + + # Verify the package was downloaded successfully + if [ ! -f "${TEMP_DEB}" ] || [ ! -s "${TEMP_DEB}" ]; then + echo "ERROR: Conda .deb package file is missing or empty" + rm -f "${TEMP_DEB}" + exit 1 + fi + + # Install the package using apt (which handles dependencies automatically) + echo "Installing conda package..." + if ! apt-get install -y "${TEMP_DEB}"; then + echo "ERROR: Failed to install conda package" + rm -f "${TEMP_DEB}" + exit 1 + fi + + # Clean up downloaded package + rm -f "${TEMP_DEB}" CONDA_SCRIPT="/opt/conda/etc/profile.d/conda.sh" . $CONDA_SCRIPT From c4d1db0f51d9506c5380be15f3c7d7d00e8af230 Mon Sep 17 00:00:00 2001 From: Abdurrahmaan Iqbal Date: Mon, 23 Feb 2026 14:15:35 +0000 Subject: [PATCH 20/78] Fix SDKMAN GLIBC 2.30 incompatibility on RHEL 8 family systems (#1575) Fix SDKMAN GLIBC 2.30 incompatibility on RHEL 8 family systems (#1568) * Initial plan * Fix SDKMAN GLIBC compatibility for RHEL 8 systems * Fix SDKMAN native binary removal for RHEL 8 systems * Disable for RHEL 8 * Update Java latest version test to check for v25 (#1574) * Initial plan * Update Java version test to check for version 25 --------- --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- src/java/devcontainer-feature.json | 4 ++-- src/java/install.sh | 21 +++++++++++++++++++++ test/java/install_latest_version.sh | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/java/devcontainer-feature.json b/src/java/devcontainer-feature.json index 4198af326..9591c3592 100644 --- a/src/java/devcontainer-feature.json +++ b/src/java/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "java", - "version": "1.6.3", + "version": "1.7.2", "name": "Java (via SDKMAN!)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/java", "description": "Installs Java, SDKMAN! (if not installed), and needed dependencies.", @@ -122,4 +122,4 @@ "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" ] -} \ No newline at end of file +} diff --git a/src/java/install.sh b/src/java/install.sh index 62fd39462..cd93eacf6 100644 --- a/src/java/install.sh +++ b/src/java/install.sh @@ -307,7 +307,28 @@ if [ ! -d "${SDKMAN_DIR}" ]; then usermod -a -G sdkman ${USERNAME} umask 0002 # Install SDKMAN + # For RHEL 8 systems (glibc 2.28), disable native version to avoid glibc compatibility issues + # SDKMAN native binaries require glibc 2.30+ which is not available in RHEL 8 / AlmaLinux 8 / Rocky 8 + if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${MAJOR_VERSION_ID}" = "8" ]; then + export SDKMAN_NATIVE_VERSION="false" + fi curl -sSL "https://get.sdkman.io?rcupdate=false" | bash + # For RHEL 8 systems, also disable native CLI in config file and remove native binaries + if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${MAJOR_VERSION_ID}" = "8" ]; then + # Disable native CLI in config to prevent future usage + # The SDKMAN config key is sdkman_native_enable (checked in sdkman-main.sh) + if [ -f "${SDKMAN_DIR}/etc/config" ]; then + if grep -q "sdkman_native_enable" "${SDKMAN_DIR}/etc/config"; then + sed -i 's/sdkman_native_enable=.*/sdkman_native_enable=false/' "${SDKMAN_DIR}/etc/config" + else + echo "sdkman_native_enable=false" >> "${SDKMAN_DIR}/etc/config" + fi + fi + # Remove native binaries if they were installed + if [ -d "${SDKMAN_DIR}/libexec" ]; then + rm -rf "${SDKMAN_DIR}/libexec" + fi + fi chown -R "${USERNAME}:sdkman" ${SDKMAN_DIR} find ${SDKMAN_DIR} -type d -print0 | xargs -d '\n' -0 chmod g+s # Add sourcing of sdkman into bashrc/zshrc files (unless disabled) diff --git a/test/java/install_latest_version.sh b/test/java/install_latest_version.sh index 007699b59..03175e767 100644 --- a/test/java/install_latest_version.sh +++ b/test/java/install_latest_version.sh @@ -9,7 +9,7 @@ echo 'public class HelloWorld { public static void main(String[] args) { System. javac HelloWorld.java check "hello world" /bin/bash -c "java HelloWorld | grep "Hello, World!"" -check "java version latest installed" grep "24" <(java --version) +check "java version latest installed" grep "25" <(java --version) # Report result reportResults From b35b810c7e9508e144bf363b2016e3bd6283c98a Mon Sep 17 00:00:00 2001 From: Abdurrahmaan Iqbal Date: Mon, 23 Feb 2026 14:16:20 +0000 Subject: [PATCH 21/78] Fix certificate verification for Ubuntu 24.04/Debian Trixie in docker features (#1577) Fix certificate verification for Ubuntu 24.04/Debian Trixie in docker features (#1569) * Initial plan * Add update-ca-certificates call after installing ca-certificates package * Bump feature versions: docker-outside-of-docker to 1.8.0, docker-in-docker to 2.16.0 * Add error handling and documentation for update-ca-certificates calls * Remove invalid test for docker-compose when v2 isn't installed --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .../devcontainer-feature.json | 178 +++++++++--------- src/docker-in-docker/install.sh | 7 + .../devcontainer-feature.json | 152 +++++++-------- src/docker-outside-of-docker/install.sh | 5 + .../docker_dash_compose_v1.sh | 1 - 5 files changed, 177 insertions(+), 166 deletions(-) diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 56520a200..48d807552 100644 --- a/src/docker-in-docker/devcontainer-feature.json +++ b/src/docker-in-docker/devcontainer-feature.json @@ -1,94 +1,94 @@ { - "id": "docker-in-docker", - "version": "2.14.0", - "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.", - "options": { - "version": { - "type": "string", - "proposals": [ - "latest", - "none", - "20.10" - ], - "default": "latest", - "description": "Select or enter a Docker/Moby Engine version. (Availability can vary by OS version.)" - }, - "moby": { - "type": "boolean", - "default": true, - "description": "Install OSS Moby build instead of Docker CE" - }, - "mobyBuildxVersion": { - "type": "string", - "default": "latest", - "description": "Install a specific version of moby-buildx when using Moby" - }, - "dockerDashComposeVersion": { - "type": "string", - "enum": [ - "none", - "v1", - "v2" - ], - "default": "v2", - "description": "Default version of Docker Compose (v1, v2 or none)" - }, - "azureDnsAutoDetection": { - "type": "boolean", - "default": true, - "description": "Allow automatically setting the dockerd DNS server when the installation script detects it is running in Azure" - }, - "dockerDefaultAddressPool": { - "type": "string", - "default": "", - "proposals": [], - "description": "Define default address pools for Docker networks. e.g. base=192.168.0.0/16,size=24" - }, - "installDockerBuildx": { - "type": "boolean", - "default": true, - "description": "Install Docker Buildx" - }, - "installDockerComposeSwitch": { - "type": "boolean", - "default": false, - "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." - }, - "disableIp6tables": { - "type": "boolean", - "default": false, - "description": "Disable ip6tables (this option is only applicable for Docker versions 27 and greater)" - } + "id": "docker-in-docker", + "version": "2.16.0", + "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.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "none", + "20.10" + ], + "default": "latest", + "description": "Select or enter a Docker/Moby Engine version. (Availability can vary by OS version.)" }, - "entrypoint": "/usr/local/share/docker-init.sh", - "privileged": true, - "containerEnv": { - "DOCKER_BUILDKIT": "1" + "moby": { + "type": "boolean", + "default": true, + "description": "Install OSS Moby build instead of Docker CE" }, - "customizations": { - "vscode": { - "extensions": [ - "ms-azuretools.vscode-containers" - ], - "settings": { - "github.copilot.chat.codeGeneration.instructions": [ - { - "text": "This dev container includes the Docker CLI (`docker`) pre-installed and available on the `PATH` for running and managing containers using a dedicated Docker daemon running inside the dev container." - } - ] - } - } + "mobyBuildxVersion": { + "type": "string", + "default": "latest", + "description": "Install a specific version of moby-buildx when using Moby" }, - "mounts": [ - { - "source": "dind-var-lib-docker-${devcontainerId}", - "target": "/var/lib/docker", - "type": "volume" - } - ], - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" - ] + "dockerDashComposeVersion": { + "type": "string", + "enum": [ + "none", + "v1", + "v2" + ], + "default": "v2", + "description": "Default version of Docker Compose (v1, v2 or none)" + }, + "azureDnsAutoDetection": { + "type": "boolean", + "default": true, + "description": "Allow automatically setting the dockerd DNS server when the installation script detects it is running in Azure" + }, + "dockerDefaultAddressPool": { + "type": "string", + "default": "", + "proposals": [], + "description": "Define default address pools for Docker networks. e.g. base=192.168.0.0/16,size=24" + }, + "installDockerBuildx": { + "type": "boolean", + "default": true, + "description": "Install Docker Buildx" + }, + "installDockerComposeSwitch": { + "type": "boolean", + "default": false, + "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." + }, + "disableIp6tables": { + "type": "boolean", + "default": false, + "description": "Disable ip6tables (this option is only applicable for Docker versions 27 and greater)" + } + }, + "entrypoint": "/usr/local/share/docker-init.sh", + "privileged": true, + "containerEnv": { + "DOCKER_BUILDKIT": "1" + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-containers" + ], + "settings": { + "github.copilot.chat.codeGeneration.instructions": [ + { + "text": "This dev container includes the Docker CLI (`docker`) pre-installed and available on the `PATH` for running and managing containers using a dedicated Docker daemon running inside the dev container." + } + ] + } + } + }, + "mounts": [ + { + "source": "dind-var-lib-docker-${devcontainerId}", + "target": "/var/lib/docker", + "type": "volume" + } + ], + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] } diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index 3f30158e5..50576ae23 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -303,6 +303,13 @@ if ! command -v git >/dev/null 2>&1; then check_packages git fi +# Update CA certificates to ensure HTTPS connections work properly +# This is especially important for Ubuntu 24.04 (Noble) and Debian Trixie +# Only run for Debian-based systems (RHEL uses update-ca-trust instead) +if [ "${ADJUSTED_ID}" = "debian" ] && command -v update-ca-certificates > /dev/null 2>&1; then + update-ca-certificates +fi + # Swap to legacy iptables for compatibility (Debian only) if [ "${ADJUSTED_ID}" = "debian" ] && type iptables-legacy > /dev/null 2>&1; then update-alternatives --set iptables /usr/sbin/iptables-legacy diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index 7314fa83d..d0039a843 100644 --- a/src/docker-outside-of-docker/devcontainer-feature.json +++ b/src/docker-outside-of-docker/devcontainer-feature.json @@ -1,80 +1,80 @@ { - "id": "docker-outside-of-docker", - "version": "1.6.5", - "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.", - "options": { - "version": { - "type": "string", - "proposals": [ - "latest", - "none", - "20.10" - ], - "default": "latest", - "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)" - }, - "moby": { - "type": "boolean", - "default": true, - "description": "Install OSS Moby build instead of Docker CE" - }, - "mobyBuildxVersion": { - "type": "string", - "default": "latest", - "description": "Install a specific version of moby-buildx when using Moby" - }, - "dockerDashComposeVersion": { - "type": "string", - "enum": [ - "none", - "v1", - "v2" - ], - "default": "v2", - "description": "Compose version to use for docker-compose (v1 or v2 or none)" - }, - "installDockerBuildx": { - "type": "boolean", - "default": true, - "description": "Install Docker Buildx" - }, - "installDockerComposeSwitch": { - "type": "boolean", - "default": true, - "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." - } + "id": "docker-outside-of-docker", + "version": "1.8.0", + "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.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "none", + "20.10" + ], + "default": "latest", + "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)" }, - "entrypoint": "/usr/local/share/docker-init.sh", - "customizations": { - "vscode": { - "extensions": [ - "ms-azuretools.vscode-containers" - ], - "settings": { - "github.copilot.chat.codeGeneration.instructions": [ - { - "text": "This dev container includes the Docker CLI (`docker`) pre-installed and available on the `PATH` for running and managing containers using the Docker daemon on the host machine." - } - ] - } - } + "moby": { + "type": "boolean", + "default": true, + "description": "Install OSS Moby build instead of Docker CE" }, - "mounts": [ - { - "source": "/var/run/docker.sock", - "target": "/var/run/docker-host.sock", - "type": "bind" - } - ], - "securityOpt": [ - "label=disable" - ], - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" - ], - "legacyIds": [ - "docker-from-docker" - ] + "mobyBuildxVersion": { + "type": "string", + "default": "latest", + "description": "Install a specific version of moby-buildx when using Moby" + }, + "dockerDashComposeVersion": { + "type": "string", + "enum": [ + "none", + "v1", + "v2" + ], + "default": "v2", + "description": "Compose version to use for docker-compose (v1 or v2 or none)" + }, + "installDockerBuildx": { + "type": "boolean", + "default": true, + "description": "Install Docker Buildx" + }, + "installDockerComposeSwitch": { + "type": "boolean", + "default": true, + "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." + } + }, + "entrypoint": "/usr/local/share/docker-init.sh", + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-containers" + ], + "settings": { + "github.copilot.chat.codeGeneration.instructions": [ + { + "text": "This dev container includes the Docker CLI (`docker`) pre-installed and available on the `PATH` for running and managing containers using the Docker daemon on the host machine." + } + ] + } + } + }, + "mounts": [ + { + "source": "/var/run/docker.sock", + "target": "/var/run/docker-host.sock", + "type": "bind" + } + ], + "securityOpt": [ + "label=disable" + ], + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ], + "legacyIds": [ + "docker-from-docker" + ] } diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index 74fd63530..242636084 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -192,6 +192,11 @@ export DEBIAN_FRONTEND=noninteractive # Install dependencies check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr wget +# Update CA certificates to ensure HTTPS connections work properly +# This is especially important for Ubuntu 24.04 (Noble) and Debian Trixie +if command -v update-ca-certificates > /dev/null 2>&1; then + update-ca-certificates +fi if ! type git > /dev/null 2>&1; then check_packages git fi diff --git a/test/docker-outside-of-docker/docker_dash_compose_v1.sh b/test/docker-outside-of-docker/docker_dash_compose_v1.sh index d95f3cf73..4ae7a9e02 100755 --- a/test/docker-outside-of-docker/docker_dash_compose_v1.sh +++ b/test/docker-outside-of-docker/docker_dash_compose_v1.sh @@ -6,7 +6,6 @@ set -e source dev-container-features-test-lib # Definition specific tests -check "docker compose" bash -c "docker compose version | grep -E '2.[0-9]+.[0-9]+'" check "docker-compose" bash -c "docker-compose --version | grep -E '1.[0-9]+.[0-9]+'" # Report result From 129c91dbe82fe1f6f7b4ee36b9f7b9e4d4cc1c95 Mon Sep 17 00:00:00 2001 From: Vatsal Gupta <40350810+gvatsal60@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:33:44 +0530 Subject: [PATCH 22/78] [#1196] Support Dependabot: Update Actions (#1197) * [#1196] Support Dependabot: Update Actions * Update .github/dependabot.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Kaniska --- .github/dependabot.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 000000000..144149689 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/.devcontainer" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" From 06d3f56b24d3fb8a79e86f429bd30f438b10a149 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Wed, 25 Feb 2026 18:55:34 +0530 Subject: [PATCH 23/78] [nvidia-cuda] - Fix installation issue on debian trixie(13) (#1591) * [nvidia-cuda] - Fix installation issue on debian trixie(13) * Update test in line with the latest defailt cuda version 12.5 * Remove uwanted submodule * Adding symlinks for duplicate test files --- src/nvidia-cuda/devcontainer-feature.json | 4 +-- src/nvidia-cuda/install.sh | 27 ++++++++++++++----- test/nvidia-cuda/install_all_options.sh | 15 ++++++----- .../install_all_options_debian_12.sh | 1 + .../install_all_options_ubuntu_noble.sh | 1 + test/nvidia-cuda/scenarios.json | 22 +++++++++++++++ test/nvidia-cuda/test.sh | 14 +++++----- 7 files changed, 61 insertions(+), 23 deletions(-) create mode 120000 test/nvidia-cuda/install_all_options_debian_12.sh create mode 120000 test/nvidia-cuda/install_all_options_ubuntu_noble.sh diff --git a/src/nvidia-cuda/devcontainer-feature.json b/src/nvidia-cuda/devcontainer-feature.json index 7dd46f7c0..477faf17a 100644 --- a/src/nvidia-cuda/devcontainer-feature.json +++ b/src/nvidia-cuda/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "nvidia-cuda", - "version": "2.0.0", + "version": "3.0.0", "name": "NVIDIA CUDA", "description": "Installs shared libraries for NVIDIA CUDA.", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/nvidia-cuda", @@ -42,7 +42,7 @@ "11.3", "11.2" ], - "default": "11.8", + "default": "12.5", "description": "Version of CUDA to install" }, "cudnnVersion": { diff --git a/src/nvidia-cuda/install.sh b/src/nvidia-cuda/install.sh index 6de935540..66e4a6834 100644 --- a/src/nvidia-cuda/install.sh +++ b/src/nvidia-cuda/install.sh @@ -62,13 +62,26 @@ esac # Add NVIDIA's package repository to apt so that we can download packages # Updating the repo to ubuntu2204 as ubuntu 20.04 is going out of support. NVIDIA_REPO_URL="https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/$NVIDIA_ARCH" -KEYRING_PACKAGE="cuda-keyring_1.0-1_all.deb" -KEYRING_PACKAGE_URL="$NVIDIA_REPO_URL/$KEYRING_PACKAGE" -KEYRING_PACKAGE_PATH="$(mktemp -d)" -KEYRING_PACKAGE_FILE="$KEYRING_PACKAGE_PATH/$KEYRING_PACKAGE" -wget -O "$KEYRING_PACKAGE_FILE" "$KEYRING_PACKAGE_URL" -apt-get install -yq "$KEYRING_PACKAGE_FILE" -apt-get update -yq + + +if [ "${ID}" = "debian" ] && [ "${VERSION_CODENAME}" = "trixie" ]; then + echo "(!) Temporary workaround on debian:trixie: bypassing NVIDIA repo signature checks" + cat > /etc/apt/sources.list.d/cuda.list < -check "cuda-11+nvtx" test -e '/usr/local/cuda-11/targets/x86_64-linux/include/nvtx3' +# Check installation of cuda-nvtx-12- +check "cuda-12+nvtx" test -e '/usr/local/cuda-12.5/targets/x86_64-linux/include/nvtx3' -# Check installation of cuda-nvcc-11- -check "cuda-11+nvcc" test -e '/usr/local/cuda-11/bin/nvcc' +# Check installation of cuda-nvcc-12- +check "cuda-12+nvcc" test -e '/usr/local/cuda-12.5/bin/nvcc' # Report result reportResults + diff --git a/test/nvidia-cuda/install_all_options_debian_12.sh b/test/nvidia-cuda/install_all_options_debian_12.sh new file mode 120000 index 000000000..52a199770 --- /dev/null +++ b/test/nvidia-cuda/install_all_options_debian_12.sh @@ -0,0 +1 @@ +install_all_options.sh \ No newline at end of file diff --git a/test/nvidia-cuda/install_all_options_ubuntu_noble.sh b/test/nvidia-cuda/install_all_options_ubuntu_noble.sh new file mode 120000 index 000000000..52a199770 --- /dev/null +++ b/test/nvidia-cuda/install_all_options_ubuntu_noble.sh @@ -0,0 +1 @@ +install_all_options.sh \ No newline at end of file diff --git a/test/nvidia-cuda/scenarios.json b/test/nvidia-cuda/scenarios.json index 82c84f119..e8c0c6c22 100644 --- a/test/nvidia-cuda/scenarios.json +++ b/test/nvidia-cuda/scenarios.json @@ -10,6 +10,28 @@ } } }, + "install_all_options_debian_12": { + "image": "debian:12", + "features": { + "nvidia-cuda": { + "installCudnn": true, + "installCudnnDev": true, + "installNvtx": true, + "installToolkit": true + } + } + }, + "install_all_options_ubuntu_noble": { + "image": "ubuntu:noble", + "features": { + "nvidia-cuda": { + "installCudnn": true, + "installCudnnDev": true, + "installNvtx": true, + "installToolkit": true + } + } + }, "install_cudnn_nvxt_version": { "image": "debian", "features": { diff --git a/test/nvidia-cuda/test.sh b/test/nvidia-cuda/test.sh index 5c56b178d..1415340ca 100644 --- a/test/nvidia-cuda/test.sh +++ b/test/nvidia-cuda/test.sh @@ -5,16 +5,16 @@ set -e # Optional: Import test library source dev-container-features-test-lib -check "cuda version" test -d /usr/local/cuda-11.8 +check "cuda version" test -d /usr/local/cuda-12.5 -# Check installation of cuda-libraries-11- -check "libcudart.so.11.0" test 1 -eq "$(find /usr -name 'libcudart.so.11.0' | wc -l)" -check "libcublas.so.11" test 1 -eq "$(find /usr -name 'libcublas.so.11' | wc -l)" -check "libcublasLt.so.11" test 1 -eq "$(find /usr -name 'libcublasLt.so.11' | wc -l)" -check "libcufft.so.10" test 1 -eq "$(find /usr -name 'libcufft.so.10' | wc -l)" +# Check installation of cuda-libraries-12- +check "libcudart.so.12" test 1 -eq "$(find /usr -name 'libcudart.so.12' | wc -l)" +check "libcublas.so.12" test 1 -eq "$(find /usr -name 'libcublas.so.12' | wc -l)" +check "libcublasLt.so.12" test 1 -eq "$(find /usr -name 'libcublasLt.so.12' | wc -l)" +check "libcufft.so.11" test 1 -eq "$(find /usr -name 'libcufft.so.11' | wc -l)" check "libcurand.so.10" test 1 -eq "$(find /usr -name 'libcurand.so.10' | wc -l)" check "libcusolver.so.11" test 1 -eq "$(find /usr -name 'libcusolver.so.11' | wc -l)" -check "libcusparse.so.11" test 1 -eq "$(find /usr -name 'libcusparse.so.11' | wc -l)" +check "libcusparse.so.12" test 1 -eq "$(find /usr -name 'libcusparse.so.12' | wc -l)" # Report result reportResults From d79c223de2849c671c806c1a86ca9302993bac8c Mon Sep 17 00:00:00 2001 From: Abdurrahmaan Iqbal Date: Thu, 26 Feb 2026 11:55:02 +0000 Subject: [PATCH 24/78] Fix kubectl-helm-minikube installation failures on debian:11 and ubuntu:focal (#1578) * Fix kubectl-helm-minikube installation failures on debian:11 and ubuntu:focal (#1567) * Initial plan * Fix kubectl SHA256 download URL and bump version to 1.3.1 Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Add timeout and fallback for kubectl version fetching Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Improve error messages and diagnostics for kubectl version fetching Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Address code review feedback: clean up error handling Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Improve error message to reference VERSION option Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Use hardcoded fallback version instead of git tags fallback Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Add version validation and comment for fallback version Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Extract fallback version to constant at top of file Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> * Update fallback version of kubectl to v1.35.1 * Add alternative URL fallback before using hardcoded version Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: abdurriq <137001048+abdurriq@users.noreply.github.com> Co-authored-by: Abdurrahmaan Iqbal * Fix kubectl SHA256 URL in install script * Use configurable fallback kubectl version instead of just hardcoded one --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .../devcontainer-feature.json | 119 +++++++++--------- src/kubectl-helm-minikube/install.sh | 13 +- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/kubectl-helm-minikube/devcontainer-feature.json b/src/kubectl-helm-minikube/devcontainer-feature.json index 410a909e4..a88aebde6 100644 --- a/src/kubectl-helm-minikube/devcontainer-feature.json +++ b/src/kubectl-helm-minikube/devcontainer-feature.json @@ -1,61 +1,66 @@ { - "id": "kubectl-helm-minikube", - "version": "1.2.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.", - "options": { - "version": { - "type": "string", - "proposals": [ - "latest", - "none", - "1.23", - "1.22", - "1.21", - "none" - ], - "default": "latest", - "description": "Select or enter a Kubernetes version to install" - }, - "helm": { - "type": "string", - "proposals": [ - "latest", - "none" - ], - "default": "latest", - "description": "Select or enter a Helm version to install" - }, - "minikube": { - "type": "string", - "proposals": [ - "latest", - "none" - ], - "default": "latest", - "description": "Select or enter a Minikube version to install" - } + "id": "kubectl-helm-minikube", + "version": "1.3.1", + "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.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "none", + "1.23", + "1.22", + "1.21", + "none" + ], + "default": "latest", + "description": "Select or enter a Kubernetes version to install" }, - "mounts": [ - { - "source": "minikube-config", - "target": "/home/vscode/.minikube", - "type": "volume" - } - ], - "customizations": { - "vscode": { - "settings": { - "github.copilot.chat.codeGeneration.instructions": [ - { - "text": "This dev container includes kubectl, Helm, optionally minikube, and needed dependencies pre-installed and available on the `PATH`. When configuring Ingress for your Kubernetes cluster, note that by default Kubernetes will bind to a specific interface's IP rather than localhost or all interfaces. This is why you need to use the Kubernetes Node's IP when connecting - even if there's only one Node as in the case of Minikube." - } - ] - } - } + "helm": { + "type": "string", + "proposals": [ + "latest", + "none" + ], + "default": "latest", + "description": "Select or enter a Helm version to install" }, - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" - ] + "minikube": { + "type": "string", + "proposals": [ + "latest", + "none" + ], + "default": "latest", + "description": "Select or enter a Minikube version to install" + }, + "kubectlFallbackVersion": { + "type": "string", + "default": "v1.35.1", + "description": "Fallback kubectl version to use when the latest stable version cannot be fetched" + } + }, + "mounts": [ + { + "source": "minikube-config", + "target": "/home/vscode/.minikube", + "type": "volume" + } + ], + "customizations": { + "vscode": { + "settings": { + "github.copilot.chat.codeGeneration.instructions": [ + { + "text": "This dev container includes kubectl, Helm, optionally minikube, and needed dependencies pre-installed and available on the `PATH`. When configuring Ingress for your Kubernetes cluster, note that by default Kubernetes will bind to a specific interface's IP rather than localhost or all interfaces. This is why you need to use the Kubernetes Node's IP when connecting - even if there's only one Node as in the case of Minikube." + } + ] + } + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] } diff --git a/src/kubectl-helm-minikube/install.sh b/src/kubectl-helm-minikube/install.sh index f0cf1c946..40901d3cb 100755 --- a/src/kubectl-helm-minikube/install.sh +++ b/src/kubectl-helm-minikube/install.sh @@ -12,6 +12,9 @@ set -e # Clean up rm -rf /var/lib/apt/lists/* +# Fallback version when stable.txt cannot be fetched +KUBECTL_FALLBACK_VERSION="${KUBECTLFALLBACKVERSION:-"v1.35.1"}" + KUBECTL_VERSION="${VERSION:-"latest"}" HELM_VERSION="${HELM:-"latest"}" MINIKUBE_VERSION="${MINIKUBE:-"latest"}" # latest is also valid @@ -164,7 +167,15 @@ if [ ${KUBECTL_VERSION} != "none" ]; then # Install the kubectl, verify checksum echo "Downloading kubectl..." if [ "${KUBECTL_VERSION}" = "latest" ] || [ "${KUBECTL_VERSION}" = "lts" ] || [ "${KUBECTL_VERSION}" = "current" ] || [ "${KUBECTL_VERSION}" = "stable" ]; then - KUBECTL_VERSION="$(curl -sSL https://dl.k8s.io/release/stable.txt)" + KUBECTL_VERSION="$(curl -fsSL --connect-timeout 10 --max-time 30 https://dl.k8s.io/release/stable.txt 2>/dev/null | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' || echo "")" + if [ -z "${KUBECTL_VERSION}" ]; then + echo "(!) Failed to fetch kubectl stable version from dl.k8s.io, trying alternative URL..." + KUBECTL_VERSION="$(curl -fsSL --connect-timeout 10 --max-time 30 https://storage.googleapis.com/kubernetes-release/release/stable.txt 2>/dev/null | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' || echo "")" + fi + if [ -z "${KUBECTL_VERSION}" ]; then + echo "(!) Failed to fetch kubectl stable version from both URLs. Using fallback version ${KUBECTL_FALLBACK_VERSION}" + KUBECTL_VERSION="${KUBECTL_FALLBACK_VERSION}" + fi else find_version_from_git_tags KUBECTL_VERSION https://github.com/kubernetes/kubernetes fi From 03ea24c7d4311c9ef342985c3bd2531c105cbbf5 Mon Sep 17 00:00:00 2001 From: Vatsal Gupta <40350810+gvatsal60@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:01:40 +0530 Subject: [PATCH 25/78] #1587 - Fix: Remove temporary Go tools directory (#1588) * Fix: Remove temporary Go tools directory * Bump go version & Fixed variable expansion --------- Co-authored-by: Kaniska --- src/go/devcontainer-feature.json | 2 +- src/go/install.sh | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/go/devcontainer-feature.json b/src/go/devcontainer-feature.json index f98e65a7f..8872d6374 100644 --- a/src/go/devcontainer-feature.json +++ b/src/go/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "go", - "version": "1.3.2", + "version": "1.3.3", "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 85fea5dc4..4286c08a8 100755 --- a/src/go/install.sh +++ b/src/go/install.sh @@ -301,10 +301,11 @@ GO_TOOLS="\ if [ "${INSTALL_GO_TOOLS}" = "true" ]; then echo "Installing common Go tools..." export PATH=${TARGET_GOROOT}/bin:${PATH} - mkdir -p /tmp/gotools /usr/local/etc/vscode-dev-containers ${TARGET_GOPATH}/bin - cd /tmp/gotools export GOPATH=/tmp/gotools - export GOCACHE=/tmp/gotools/cache + export GOCACHE="${GOPATH}/cache" + + mkdir -p "${GOPATH}" /usr/local/etc/vscode-dev-containers "${TARGET_GOPATH}/bin" + cd "${GOPATH}" # Use go get for versions of go under 1.16 go_install_command=install @@ -316,10 +317,9 @@ if [ "${INSTALL_GO_TOOLS}" = "true" ]; then (echo "${GO_TOOLS}" | xargs -n 1 go ${go_install_command} -v )2>&1 | tee -a /usr/local/etc/vscode-dev-containers/go.log - # Move Go tools into path and clean up - if [ -d /tmp/gotools/bin ]; then - mv /tmp/gotools/bin/* ${TARGET_GOPATH}/bin/ - rm -rf /tmp/gotools + # Move Go tools into path + if [ -d "${GOPATH}/bin" ]; then + mv "${GOPATH}/bin"/* "${TARGET_GOPATH}/bin/" fi # Install golangci-lint from precompiled binares @@ -332,6 +332,9 @@ if [ "${INSTALL_GO_TOOLS}" = "true" ]; then curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ sh -s -- -b "${TARGET_GOPATH}/bin" "v${GOLANGCILINT_VERSION}" fi + + # Remove Go tools temp directory + rm -rf "${GOPATH}" fi From 065b4d466f4623ab1c5aeb3e3b323d88cf69d190 Mon Sep 17 00:00:00 2001 From: Karsten Becker <567973+KarstenB@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:52:53 +0100 Subject: [PATCH 26/78] =?UTF-8?q?fix(docker-in-docker):=20create=20/usr/lo?= =?UTF-8?q?cal/share=20directory=20if=20it=20doesn't=20exist=20before=20wr?= =?UTF-8?q?iting=20=E2=80=A6=20(#1594)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create /usr/local/share directory if it doesn't exist before writing docker-init.sh --- src/docker-in-docker/devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 48d807552..4c792e8f4 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": "2.16.0", + "version": "2.16.1", "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 50576ae23..5af320b0b 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -861,6 +861,10 @@ if [ "$DISABLE_IP6_TABLES" == true ]; then fi fi +if [ ! -d /usr/local/share ]; then + mkdir -p /usr/local/share +fi + tee /usr/local/share/docker-init.sh > /dev/null \ << EOF #!/bin/sh From 0c2cd3f94456726bae0027cee4c8a0f583667b98 Mon Sep 17 00:00:00 2001 From: 7006 <22399553+7006@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:55:54 +0200 Subject: [PATCH 27/78] Remove duplicated CentOS 7 mirrorlist update logic (#1586) * Bump version from 2.5.6 to 2.5.7 * Remove duplicated CentOS 7 mirrorlist update logic Remove duplicated CentOS 7 mirrorlist handling introduced in this commit https://github.com/devcontainers/features/commit/52c79b4963879dd941c67b583199ec7966e41ab4 --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 14056e3a9..4ebbd3074 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.5.6", + "version": "2.5.7", "name": "Common Utilities", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/common-utils", "description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.", diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index b0fd2f3b0..3f6b13477 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -378,14 +378,6 @@ if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${VERSION_CODENAME-}" = "centos7" ]; then sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo fi -if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${VERSION_CODENAME-}" = "centos7" ]; then - # As of 1 July 2024, mirrorlist.centos.org no longer exists. - # Update the repo files to reference vault.centos.org. - sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo - sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo - sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo -fi - # Install packages for appropriate OS case "${ADJUSTED_ID}" in "debian") From e04e9cee60122521c0e717a1d4d4d5737bd5f6a3 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 11 Mar 2026 14:26:32 +0100 Subject: [PATCH 28/78] Fix dotnet latest resolution (#1598) * Fix dotnet latest resolution * Restore latest version check in test * Avoid silently ignoring CDN errors * Clarify dotnet latest target selection * test(dotnet): source staged feature helper script --- src/dotnet/NOTES.md | 2 +- src/dotnet/README.md | 10 +-- src/dotnet/install.sh | 2 +- src/dotnet/scripts/dotnet-helpers.sh | 67 +++++++++++-------- test/dotnet/dotnet_helpers.sh | 36 +--------- test/dotnet/install_dotnet_lts.sh | 2 +- .../dotnet/install_dotnet_specific_release.sh | 2 +- test/dotnet/install_dotnet_workloads.sh | 3 - test/dotnet/scenarios.json | 2 +- 9 files changed, 50 insertions(+), 76 deletions(-) diff --git a/src/dotnet/NOTES.md b/src/dotnet/NOTES.md index ff52835b0..953372381 100644 --- a/src/dotnet/NOTES.md +++ b/src/dotnet/NOTES.md @@ -67,7 +67,7 @@ Installing .NET workloads. Multiple workloads can be specified as comma-separate ``` json "features": { "ghcr.io/devcontainers/features/dotnet:2": { - "workloads": "aspire, wasm-tools" + "workloads": "wasm-tools" } } ``` diff --git a/src/dotnet/README.md b/src/dotnet/README.md index fecaeb3f3..8244b151d 100644 --- a/src/dotnet/README.md +++ b/src/dotnet/README.md @@ -15,10 +15,10 @@ This Feature installs the latest .NET SDK, which includes the .NET CLI and the s | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| -| version | Select or enter a .NET SDK version. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | latest | -| additionalVersions | Enter additional .NET SDK versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | - | -| dotnetRuntimeVersions | Enter additional .NET runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | - | -| aspNetCoreRuntimeVersions | Enter additional ASP.NET Core runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | - | +| version | Select or enter a .NET SDK version. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | latest | +| additionalVersions | Enter additional .NET SDK versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | +| dotnetRuntimeVersions | Enter additional .NET runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | +| aspNetCoreRuntimeVersions | Enter additional ASP.NET Core runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | | workloads | Enter additional .NET SDK workloads, separated by commas. Use 'dotnet workload search' to learn what workloads are available to install. | string | - | ## Customizations @@ -95,7 +95,7 @@ Installing .NET workloads. Multiple workloads can be specified as comma-separate ``` json "features": { "ghcr.io/devcontainers/features/dotnet:2": { - "workloads": "aspire, wasm-tools" + "workloads": "wasm-tools" } } ``` diff --git a/src/dotnet/install.sh b/src/dotnet/install.sh index a6bde1ebd..d2b06cd0e 100644 --- a/src/dotnet/install.sh +++ b/src/dotnet/install.sh @@ -105,7 +105,7 @@ done # Install .NET versions and dependencies # icu-devtools includes dependencies for .NET -check_packages wget ca-certificates icu-devtools +check_packages wget ca-certificates icu-devtools jq for version in "${versions[@]}"; do read -r clean_version quality < <(parse_version_and_quality "$version") diff --git a/src/dotnet/scripts/dotnet-helpers.sh b/src/dotnet/scripts/dotnet-helpers.sh index 2ef8796eb..d2dbc4534 100644 --- a/src/dotnet/scripts/dotnet-helpers.sh +++ b/src/dotnet/scripts/dotnet-helpers.sh @@ -8,40 +8,49 @@ # Maintainer: The Dev Container spec maintainers DOTNET_SCRIPTS=$(dirname "${BASH_SOURCE[0]}") DOTNET_INSTALL_SCRIPT="$DOTNET_SCRIPTS/vendor/dotnet-install.sh" +DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json" -# Prints the latest dotnet version in the specified channel -# Usage: fetch_latest_version_in_channel [] -# Example: fetch_latest_version_in_channel "LTS" -# Example: fetch_latest_version_in_channel "6.0" "dotnet" -# Example: fetch_latest_version_in_channel "6.0" "aspnetcore" -fetch_latest_version_in_channel() { - local channel="$1" - local runtime="$2" - if [ "$runtime" = "dotnet" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Runtime/$channel/latest.version" - elif [ "$runtime" = "aspnetcore" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/$channel/latest.version" - else - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/$channel/latest.version" - fi -} - -# Prints the latest dotnet version -# Usage: fetch_latest_version [] +# Prints the latest active dotnet version from the releases index. +# Usage: fetch_latest_version [] +# With no target, resolves the latest SDK version. +# With "sdk", resolves the latest SDK version explicitly. +# With "dotnet" or "aspnetcore", resolves the latest runtime version. +# Note: the upstream releases index only distinguishes SDK vs runtime for +# latest resolution, so "dotnet" and "aspnetcore" currently resolve to the +# same version. # Example: fetch_latest_version +# Example: fetch_latest_version "sdk" # Example: fetch_latest_version "dotnet" # Example: fetch_latest_version "aspnetcore" fetch_latest_version() { - local runtime="$1" - local sts_version - local lts_version - sts_version=$(fetch_latest_version_in_channel "STS" "$runtime") - lts_version=$(fetch_latest_version_in_channel "LTS" "$runtime") - if [[ "$sts_version" > "$lts_version" ]]; then - echo "$sts_version" - else - echo "$lts_version" - fi + local target="$1" + local version_field="" + local releases_index="" + + case "$target" in + ""|sdk) + version_field="latest-sdk" + ;; + dotnet|aspnetcore) + version_field="latest-runtime" + ;; + *) + echo "Unsupported target '$target'. Expected 'sdk', 'dotnet', or 'aspnetcore'." >&2 + return 1 + ;; + esac + + releases_index="$(wget -qO- "$DOTNET_RELEASES_INDEX_URL")" || return $? + + printf '%s\n' "$releases_index" \ + | jq -er --arg version_field "$version_field" ' + .["releases-index"] + | map( + select(."support-phase" == "active") + | .[$version_field] + ) + | .[0] + ' } # Installs a version of the .NET SDK diff --git a/test/dotnet/dotnet_helpers.sh b/test/dotnet/dotnet_helpers.sh index 01e554f66..20671f84b 100644 --- a/test/dotnet/dotnet_helpers.sh +++ b/test/dotnet/dotnet_helpers.sh @@ -1,39 +1,7 @@ #!/bin/bash -# Prints the latest dotnet version in the specified channel -# Usage: fetch_latest_version_in_channel [] -# Example: fetch_latest_version_in_channel "LTS" -# Example: fetch_latest_version_in_channel "6.0" "dotnet" -# Example: fetch_latest_version_in_channel "6.0" "aspnetcore" -fetch_latest_version_in_channel() { - local channel="$1" - local runtime="$2" - if [ "$runtime" = "dotnet" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Runtime/$channel/latest.version" - elif [ "$runtime" = "aspnetcore" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/$channel/latest.version" - else - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/$channel/latest.version" - fi -} - -# Prints the latest dotnet version -# Usage: fetch_latest_version [] -# Example: fetch_latest_version -# Example: fetch_latest_version "dotnet" -# Example: fetch_latest_version "aspnetcore" -fetch_latest_version() { - local runtime="$1" - local sts_version - local lts_version - sts_version=$(fetch_latest_version_in_channel "STS" "$runtime") - lts_version=$(fetch_latest_version_in_channel "LTS" "$runtime") - if [[ "$sts_version" > "$lts_version" ]]; then - echo "$sts_version" - else - echo "$lts_version" - fi -} +# Include the same helper functions used by the install script +source ".devcontainer/dotnet/scripts/dotnet-helpers.sh" # Asserts that the specified .NET SDK version is installed # Returns a non-zero exit code if the check fails diff --git a/test/dotnet/install_dotnet_lts.sh b/test/dotnet/install_dotnet_lts.sh index da9175c15..a62c5937b 100644 --- a/test/dotnet/install_dotnet_lts.sh +++ b/test/dotnet/install_dotnet_lts.sh @@ -13,7 +13,7 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -expected=$(fetch_latest_version_in_channel "LTS") +expected=$(wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/LTS/latest.version") check "Latest LTS version installed" \ is_dotnet_sdk_version_installed "$expected" diff --git a/test/dotnet/install_dotnet_specific_release.sh b/test/dotnet/install_dotnet_specific_release.sh index 1ef587945..961cb47ab 100644 --- a/test/dotnet/install_dotnet_specific_release.sh +++ b/test/dotnet/install_dotnet_specific_release.sh @@ -13,7 +13,7 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -expected=$(fetch_latest_version_in_channel "10.0") +expected=$(wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0/latest.version") check ".NET Core SDK 10.0 installed" \ is_dotnet_sdk_version_installed "$expected" diff --git a/test/dotnet/install_dotnet_workloads.sh b/test/dotnet/install_dotnet_workloads.sh index 37c86a2d4..c4885664a 100644 --- a/test/dotnet/install_dotnet_workloads.sh +++ b/test/dotnet/install_dotnet_workloads.sh @@ -13,9 +13,6 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -check "Aspire is installed" \ -is_dotnet_workload_installed "aspire" - check "WASM tools are installed" \ is_dotnet_workload_installed "wasm-tools" diff --git a/test/dotnet/scenarios.json b/test/dotnet/scenarios.json index 62e2f1a46..52c35b2b0 100644 --- a/test/dotnet/scenarios.json +++ b/test/dotnet/scenarios.json @@ -89,7 +89,7 @@ "features": { "dotnet": { "version": "latest", - "workloads": "aspire, wasm-tools" + "workloads": "wasm-tools" } } } From 372e2d21c8080aa6bea117180b6d42094523b1d7 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 11 Mar 2026 15:35:43 +0100 Subject: [PATCH 29/78] dotnet: set up tab completions for SDK 10+ (#1596) * dotnet: set up tab completions for SDK 10+ Generate bash, zsh, and fish completion scripts using 'dotnet completions script' and place them in the standard system-wide completion directories: - /usr/share/bash-completion/completions/dotnet - /usr/share/zsh/site-functions/_dotnet - /usr/share/fish/vendor_completions.d/dotnet.fish Gated behind SDK 10+ via version check since the 'dotnet completions script' command is only available starting with .NET 10. Skipped for runtime-only installs. Reference: https://learn.microsoft.com/en-us/dotnet/core/tools/enable-tab-autocomplete * Add tab completion assertions * Sync dotnet docs options with feature metadata --- src/dotnet/NOTES.md | 22 +++++++++++++-- src/dotnet/README.md | 3 +- src/dotnet/devcontainer-feature.json | 9 ++++-- src/dotnet/install.sh | 5 ++++ src/dotnet/scripts/dotnet-helpers.sh | 42 ++++++++++++++++++++++++++++ test/dotnet/test.sh | 11 +++++++- 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/dotnet/NOTES.md b/src/dotnet/NOTES.md index 953372381..c5d6f6071 100644 --- a/src/dotnet/NOTES.md +++ b/src/dotnet/NOTES.md @@ -57,7 +57,7 @@ Installing only the .NET Runtime or the ASP.NET Core Runtime. (The SDK includes "ghcr.io/devcontainers/features/dotnet:2": { "version": "none", "dotnetRuntimeVersions": "latest, lts", - "aspnetCoreRuntimeVersions": "latest, lts", + "aspNetCoreRuntimeVersions": "latest, lts", } } ``` @@ -80,7 +80,7 @@ Installing prerelease builds. Supports `preview` and `daily` suffixes. "version": "10.0-preview", "additionalVersions": "10.0.1xx-daily", "dotnetRuntimeVersions": "10.0-daily", - "aspnetCoreRuntimeVersions": "10.0-daily" + "aspNetCoreRuntimeVersions": "10.0-daily" } } ``` @@ -90,3 +90,21 @@ Installing prerelease builds. Supports `preview` and `daily` suffixes. This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. `bash` is required to execute the `install.sh` script. + +## Tab completions + +When using .NET SDK 10 or newer, tab completions for the `dotnet` CLI are automatically installed for bash, zsh, and fish. The completion scripts are placed in the standard system-wide directories so they work for all users: + +- **Bash**: `/usr/share/bash-completion/completions/dotnet` +- **Zsh**: `/usr/share/zsh/site-functions/_dotnet` +- **Fish**: `/usr/share/fish/vendor_completions.d/dotnet.fish` + +To disable this, set `tabCompletions` to `false`: + +``` json +"features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "tabCompletions": false + } +} +``` diff --git a/src/dotnet/README.md b/src/dotnet/README.md index 8244b151d..ceb9c544f 100644 --- a/src/dotnet/README.md +++ b/src/dotnet/README.md @@ -20,6 +20,7 @@ This Feature installs the latest .NET SDK, which includes the .NET CLI and the s | dotnetRuntimeVersions | Enter additional .NET runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | | aspNetCoreRuntimeVersions | Enter additional ASP.NET Core runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | | workloads | Enter additional .NET SDK workloads, separated by commas. Use 'dotnet workload search' to learn what workloads are available to install. | string | - | +| tabCompletions | Install shell tab completions for the dotnet CLI. Requires SDK 10 or newer. | boolean | true | ## Customizations @@ -85,7 +86,7 @@ Installing only the .NET Runtime or the ASP.NET Core Runtime. (The SDK includes "ghcr.io/devcontainers/features/dotnet:2": { "version": "none", "dotnetRuntimeVersions": "latest, lts", - "aspnetCoreRuntimeVersions": "latest, lts", + "aspNetCoreRuntimeVersions": "latest, lts", } } ``` diff --git a/src/dotnet/devcontainer-feature.json b/src/dotnet/devcontainer-feature.json index 7389b8398..4bd3168c7 100644 --- a/src/dotnet/devcontainer-feature.json +++ b/src/dotnet/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "dotnet", - "version": "2.4.2", + "version": "2.5.0", "name": "Dotnet CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/dotnet", "description": "This Feature installs the latest .NET SDK, which includes the .NET CLI and the shared runtime. Options are provided to choose a different version or additional versions.", @@ -39,6 +39,11 @@ "type": "string", "default": "", "description": "Enter additional .NET SDK workloads, separated by commas. Use 'dotnet workload search' to learn what workloads are available to install." + }, + "tabCompletions": { + "type": "boolean", + "default": true, + "description": "Install shell tab completions for the dotnet CLI. Requires SDK 10 or newer." } }, "containerEnv": { @@ -64,4 +69,4 @@ "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" ] -} +} \ No newline at end of file diff --git a/src/dotnet/install.sh b/src/dotnet/install.sh index d2b06cd0e..a8e8436e5 100644 --- a/src/dotnet/install.sh +++ b/src/dotnet/install.sh @@ -11,6 +11,7 @@ ADDITIONAL_VERSIONS="${ADDITIONALVERSIONS:-""}" DOTNET_RUNTIME_VERSIONS="${DOTNETRUNTIMEVERSIONS:-""}" ASPNETCORE_RUNTIME_VERSIONS="${ASPNETCORERUNTIMEVERSIONS:-""}" WORKLOADS="${WORKLOADS:-""}" +TAB_COMPLETIONS="${TABCOMPLETIONS:-"true"}" # Prevent "Welcome to .NET" message from dotnet export DOTNET_NOLOGO=true @@ -146,6 +147,10 @@ if [ ! -e /usr/bin/dotnet ]; then ln --symbolic "$DOTNET_ROOT/dotnet" /usr/bin/dotnet fi +if [ "$TAB_COMPLETIONS" = "true" ]; then + install_completions +fi + # Add .NET Core SDK tools to PATH for bash and zsh users # This is where 'dotnet tool install --global ' installs tools to # Use single-quoted EOF to defer $PATH expansion until sourcing the file diff --git a/src/dotnet/scripts/dotnet-helpers.sh b/src/dotnet/scripts/dotnet-helpers.sh index d2dbc4534..e4c90819a 100644 --- a/src/dotnet/scripts/dotnet-helpers.sh +++ b/src/dotnet/scripts/dotnet-helpers.sh @@ -193,4 +193,46 @@ parse_version_and_quality() { quality="" fi echo "$clean_version" "$quality" +} + +# Checks if the installed .NET SDK is at least the given major version. +# Returns 0 (true) if the SDK major version >= the specified version, 1 otherwise. +# Also returns 1 if no SDK is installed (e.g. runtime-only installs). +# Usage: is_at_least_sdk_version +# Example: is_at_least_sdk_version 10 +is_at_least_sdk_version() { + local required_major="$1" + local dotnet_version + dotnet_version=$("$DOTNET_ROOT/dotnet" --version 2>/dev/null || true) + local major_version="${dotnet_version%%.*}" + [[ "$major_version" =~ ^[0-9]+$ ]] && [ "$major_version" -ge "$required_major" ] +} + +# Sets up dotnet tab completions for bash, zsh, and fish. +# The 'dotnet completions script' command is only available in .NET SDK 10+. +# Older SDKs and runtime-only installs will naturally skip this since the +# command won't be available. +# Reference: https://learn.microsoft.com/en-us/dotnet/core/tools/enable-tab-autocomplete +# Completion scripts are generated at install time and placed in the standard +# system-wide completion directories, which are auto-discovered by +# bash-completion, zsh, and fish without modifying any rc files. +install_completions() { + if ! is_at_least_sdk_version 10; then + echo "Skipping dotnet tab completions (requires SDK 10+)." + return + fi + + echo "Setting up dotnet tab completions..." + + # Bash: drop into the standard bash-completion directory + mkdir -p /usr/share/bash-completion/completions + "$DOTNET_ROOT/dotnet" completions script bash > /usr/share/bash-completion/completions/dotnet + + # Zsh: drop into the standard site-functions directory + mkdir -p /usr/share/zsh/site-functions + "$DOTNET_ROOT/dotnet" completions script zsh > /usr/share/zsh/site-functions/_dotnet + + # Fish: drop into the standard vendor completions directory + mkdir -p /usr/share/fish/vendor_completions.d + "$DOTNET_ROOT/dotnet" completions script fish > /usr/share/fish/vendor_completions.d/dotnet.fish } \ No newline at end of file diff --git a/test/dotnet/test.sh b/test/dotnet/test.sh index 11cc73126..7c5591773 100644 --- a/test/dotnet/test.sh +++ b/test/dotnet/test.sh @@ -21,9 +21,18 @@ test -L /usr/bin/dotnet -a "$(readlink -f /usr/bin/dotnet)" = "$DOTNET_ROOT/dotn expected=$(fetch_latest_version) -check "Latest .NET SDK version installed" \ +check "Latest .NET SDK version $expected installed" \ is_dotnet_sdk_version_installed "$expected" +check "Bash completion script installed" \ +test -s /usr/share/bash-completion/completions/dotnet + +check "Zsh completion script installed" \ +test -s /usr/share/zsh/site-functions/_dotnet + +check "Fish completion script installed" \ +test -s /usr/share/fish/vendor_completions.d/dotnet.fish + # Report results # If any of the checks above exited with a non-zero exit code, the test will fail. reportResults \ No newline at end of file From 0292aeecfcee6a82ddc6875d2b217affc327bb7b Mon Sep 17 00:00:00 2001 From: sireeshajonnalagadda Date: Thu, 12 Mar 2026 17:23:56 +0530 Subject: [PATCH 30/78] update docker-compose version to latest and add new test scenarios (#1571) * update docker-compose version to latest and add new test scenarios * Fix JSON formatting in devcontainer-feature.json * Update default docker-compose version to 'latest' * Update default docker-compose version to 'latest' * Version bump * symlink --------- Co-authored-by: Kaniska --- src/docker-outside-of-docker/README.md | 2 +- .../devcontainer-feature.json | 8 +++++--- src/docker-outside-of-docker/install.sh | 2 +- .../docker_dash_compose_latest_moby.sh | 1 + .../docker_dash_compose_latest_no_moby.sh | 14 +++++++++++++ test/docker-outside-of-docker/scenarios.json | 20 +++++++++++++++++++ 6 files changed, 42 insertions(+), 5 deletions(-) create mode 120000 test/docker-outside-of-docker/docker_dash_compose_latest_moby.sh create mode 100644 test/docker-outside-of-docker/docker_dash_compose_latest_no_moby.sh diff --git a/src/docker-outside-of-docker/README.md b/src/docker-outside-of-docker/README.md index 1794d42e8..e4fe0c446 100644 --- a/src/docker-outside-of-docker/README.md +++ b/src/docker-outside-of-docker/README.md @@ -20,7 +20,7 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv | version | Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.) | string | latest | | moby | Install OSS Moby build instead of Docker CE | boolean | true | | mobyBuildxVersion | Install a specific version of moby-buildx when using Moby | string | latest | -| dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none) | string | v2 | +| dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none or latest) | string | latest | | installDockerBuildx | Install Docker Buildx | boolean | true | | installDockerComposeSwitch | Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter. | boolean | true | diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index d0039a843..f2e57bd43 100644 --- a/src/docker-outside-of-docker/devcontainer-feature.json +++ b/src/docker-outside-of-docker/devcontainer-feature.json @@ -1,6 +1,7 @@ { + "id": "docker-outside-of-docker", - "version": "1.8.0", + "version": "1.9.0", "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.", @@ -29,11 +30,12 @@ "type": "string", "enum": [ "none", + "latest", "v1", "v2" ], - "default": "v2", - "description": "Compose version to use for docker-compose (v1 or v2 or none)" + "default": "latest", + "description": "Compose version to use for docker-compose (v1 or v2 or none or latest)" }, "installDockerBuildx": { "type": "boolean", diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index 242636084..6e26b13ac 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -10,7 +10,7 @@ DOCKER_VERSION="${VERSION:-"latest"}" USE_MOBY="${MOBY:-"true"}" MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}" -DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v2"}" # v1 or v2 or none +DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"latest"}" # v1 or v2 or none or latest ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}" SOURCE_SOCKET="${SOURCE_SOCKET:-"/var/run/docker-host.sock"}" diff --git a/test/docker-outside-of-docker/docker_dash_compose_latest_moby.sh b/test/docker-outside-of-docker/docker_dash_compose_latest_moby.sh new file mode 120000 index 000000000..d7e6cc99e --- /dev/null +++ b/test/docker-outside-of-docker/docker_dash_compose_latest_moby.sh @@ -0,0 +1 @@ +docker_dash_compose_latest_no_moby.sh \ No newline at end of file diff --git a/test/docker-outside-of-docker/docker_dash_compose_latest_no_moby.sh b/test/docker-outside-of-docker/docker_dash_compose_latest_no_moby.sh new file mode 100644 index 000000000..d032da9e4 --- /dev/null +++ b/test/docker-outside-of-docker/docker_dash_compose_latest_no_moby.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "docker compose" bash -c "docker compose version | grep -E '5.[0-9]+.[0-9]+'" +check "docker-compose" bash -c "docker-compose --version | grep -E '5.[0-9]+.[0-9]+'" +check "installs compose-switch as docker-compose" bash -c "[[ -f /usr/local/bin/docker-compose ]]" + +# Report result +reportResults diff --git a/test/docker-outside-of-docker/scenarios.json b/test/docker-outside-of-docker/scenarios.json index 2163e7076..3a49d594e 100644 --- a/test/docker-outside-of-docker/scenarios.json +++ b/test/docker-outside-of-docker/scenarios.json @@ -180,5 +180,25 @@ "moby": false } } + }, + "docker_dash_compose_latest_moby": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": true, + "dockerDashComposeVersion": "latest" + } + }, + "containerUser": "vscode" + }, + "docker_dash_compose_latest_no_moby": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": false, + "dockerDashComposeVersion": "latest" + } + }, + "containerUser": "vscode" } } From 20aa81fff7953399bdb89f6874b034aa7e01274f Mon Sep 17 00:00:00 2001 From: Jason D'Amour Date: Thu, 12 Mar 2026 06:34:40 -0700 Subject: [PATCH 31/78] aws-cli: add option to surpress unzip output (#1210) * aws-cli: add option to surpress unzip output * Update AWS CLI version to 1.1.3 --------- Co-authored-by: Kaniska --- src/aws-cli/devcontainer-feature.json | 7 ++++++- src/aws-cli/install.sh | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/aws-cli/devcontainer-feature.json b/src/aws-cli/devcontainer-feature.json index 54cc4b29b..ed2e20284 100644 --- a/src/aws-cli/devcontainer-feature.json +++ b/src/aws-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "aws-cli", - "version": "1.1.2", + "version": "1.1.3", "name": "AWS CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/aws-cli", "description": "Installs the AWS CLI along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.", @@ -12,6 +12,11 @@ ], "default": "latest", "description": "Select or enter an AWS CLI version." + }, + "verbose": { + "type": "boolean", + "default": true, + "description": "Suppress verbose output." } }, "customizations": { diff --git a/src/aws-cli/install.sh b/src/aws-cli/install.sh index 4ff9bfde6..ba6861074 100755 --- a/src/aws-cli/install.sh +++ b/src/aws-cli/install.sh @@ -13,6 +13,7 @@ set -e rm -rf /var/lib/apt/lists/* VERSION=${VERSION:-"latest"} +VERBOSE=${VERBOSE:-"true"} AWSCLI_GPG_KEY=FB5DB77FD5C118B80511ADA8A6310ACC4672475C AWSCLI_GPG_KEY_MATERIAL="-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -110,7 +111,12 @@ install() { exit 1 fi - unzip "${scriptZipFile}" + if [ "${VERBOSE}" = "false" ]; then + unzip -q "${scriptZipFile}" + else + unzip "${scriptZipFile}" + fi + ./aws/install # kubectl bash completion From b5b41d87b0baa6f0fda692db8ade35413f1e3e3e Mon Sep 17 00:00:00 2001 From: Kaniska Date: Mon, 23 Mar 2026 17:35:31 +0530 Subject: [PATCH 32/78] [oryx] - Upgrade DEBIAN_FLAVOR to `bookworm` (#1603) * [oryx] - Upgrade DEBIAN_FLAVOR to `bookworm` * Update documentation * Modified test scripts as per review comments --- src/oryx/README.md | 2 +- src/oryx/devcontainer-feature.json | 4 +-- src/oryx/install.sh | 2 +- test/oryx/install_dotnet_and_oryx.sh | 32 +++++++++------------- test/oryx/install_older_dotnet_and_oryx.sh | 32 +++++++++------------- test/oryx/install_prev_dotnet_and_oryx.sh | 32 +++++++++------------- test/oryx/scenarios.json | 10 +++---- test/oryx/test.sh | 15 ++++------ 8 files changed, 54 insertions(+), 75 deletions(-) diff --git a/src/oryx/README.md b/src/oryx/README.md index 7f9e62269..c115e570e 100644 --- a/src/oryx/README.md +++ b/src/oryx/README.md @@ -7,7 +7,7 @@ Installs the oryx CLI ```json "features": { - "ghcr.io/devcontainers/features/oryx:1": {} + "ghcr.io/devcontainers/features/oryx:2": {} } ``` diff --git a/src/oryx/devcontainer-feature.json b/src/oryx/devcontainer-feature.json index 860a39003..9468222e8 100644 --- a/src/oryx/devcontainer-feature.json +++ b/src/oryx/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "oryx", - "version": "1.4.1", + "version": "2.0.0", "name": "Oryx", "description": "Installs the oryx CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/oryx", @@ -10,7 +10,7 @@ "DYNAMIC_INSTALL_ROOT_DIR": "/opt", "ORYX_PREFER_USER_INSTALLED_SDKS": "true", "ORYX_DIR": "/usr/local/oryx", - "DEBIAN_FLAVOR": "focal-scm", + "DEBIAN_FLAVOR": "bookworm", "PATH": "/usr/local/oryx:${PATH}" }, "customizations": { diff --git a/src/oryx/install.sh b/src/oryx/install.sh index cf67db6b1..eeacbec39 100755 --- a/src/oryx/install.sh +++ b/src/oryx/install.sh @@ -206,7 +206,7 @@ mkdir -p "${ORYX_INSTALL_DIR}" PIP_CACHE_DIR="/usr/local/share/pip-cache/lib" mkdir -p ${PIP_CACHE_DIR} -updaterc "export ORYX_SDK_STORAGE_BASE_URL=https://oryx-cdn.microsoft.io && export ENABLE_DYNAMIC_INSTALL=true && DYNAMIC_INSTALL_ROOT_DIR=$ORYX_INSTALL_DIR && ORYX_PREFER_USER_INSTALLED_SDKS=true && export DEBIAN_FLAVOR=focal-scm" +updaterc "export ORYX_SDK_STORAGE_BASE_URL=https://oryx-cdn.microsoft.io && export ENABLE_DYNAMIC_INSTALL=true && DYNAMIC_INSTALL_ROOT_DIR=$ORYX_INSTALL_DIR && ORYX_PREFER_USER_INSTALLED_SDKS=true && export DEBIAN_FLAVOR=bookworm" chown -R "${USERNAME}:oryx" "${ORYX_INSTALL_DIR}" "${BUILD_SCRIPT_GENERATOR}" "${ORYX}" "${PIP_CACHE_DIR}" chmod -R g+r+w "${ORYX_INSTALL_DIR}" "${BUILD_SCRIPT_GENERATOR}" "${ORYX}" "${PIP_CACHE_DIR}" diff --git a/test/oryx/install_dotnet_and_oryx.sh b/test/oryx/install_dotnet_and_oryx.sh index 670d7565a..e17c4e929 100644 --- a/test/oryx/install_dotnet_and_oryx.sh +++ b/test/oryx/install_dotnet_and_oryx.sh @@ -12,21 +12,18 @@ check "Oryx version" oryx --version check "Dotnet is not removed if it is not installed by the Oryx Feature" dotnet --version # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx" ls /opt/dotnet/ | grep 2.1 +check "oryx-install-dotnet-10.0" oryx prep --skip-detection --platforms-and-versions dotnet=10.0.4 +check "dotnet-10-installed-by-oryx" ls /opt/dotnet/ | grep 10.0 -check "oryx-install-nodejs-12.22.11" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-nodejs-24.13.0" oryx prep --skip-detection --platforms-and-versions nodejs=24.13.0 +check "nodejs-24.13.0-installed-by-oryx" ls /opt/nodejs/ | grep 24.13.0 -check "oryx-install-php-7.3.25" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx" ls /opt/php/ | grep 7.3.25 - -check "oryx-install-java-12.0.2" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.5.1" oryx prep --skip-detection --platforms-and-versions php=8.5.1 +check "php-8.5.1-installed-by-oryx" ls /opt/php/ | grep 8.5.1 # Replicates Oryx's behavior for universal image mkdir -p /opt/oryx -echo "vso-focal" >> /opt/oryx/.imagetype +echo "vso-bookworm" >> /opt/oryx/.imagetype mkdir -p /opt/dotnet/lts cp -R /usr/share/dotnet/dotnet /opt/dotnet/lts @@ -34,17 +31,14 @@ cp -R /usr/share/dotnet/LICENSE.txt /opt/dotnet/lts cp -R /usr/share/dotnet/ThirdPartyNotices.txt /opt/dotnet/lts # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1-universal" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx-universal" ls /opt/dotnet/ | grep 2.1 - -check "oryx-install-nodejs-12.22.11-universal" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx-universal" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-dotnet-10.0" oryx prep --skip-detection --platforms-and-versions dotnet=10.0.4 +check "dotnet-10-installed-by-oryx" ls /opt/dotnet/ | grep 10.0 -check "oryx-install-php-7.3.25-universal" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx-universal" ls /opt/php/ | grep 7.3.25 +check "oryx-install-nodejs-24.13.0" oryx prep --skip-detection --platforms-and-versions nodejs=24.13.0 +check "nodejs-24.13.0-installed-by-oryx" ls /opt/nodejs/ | grep 24.13.0 -check "oryx-install-java-12.0.2-universal" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx-universal" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.5.1" oryx prep --skip-detection --platforms-and-versions php=8.5.1 +check "php-8.5.1-installed-by-oryx" ls /opt/php/ | grep 8.5.1 # Report result reportResults diff --git a/test/oryx/install_older_dotnet_and_oryx.sh b/test/oryx/install_older_dotnet_and_oryx.sh index da0a9162d..2846d9519 100644 --- a/test/oryx/install_older_dotnet_and_oryx.sh +++ b/test/oryx/install_older_dotnet_and_oryx.sh @@ -9,21 +9,18 @@ check "Oryx version" oryx --version check "Dotnet is not removed if it is not installed by the Oryx Feature" dotnet --version # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx" ls /opt/dotnet/ | grep 2.1 +check "oryx-install-dotnet-6.0" oryx prep --skip-detection --platforms-and-versions dotnet=6.0.23 +check "dotnet-6-installed-by-oryx" ls /opt/dotnet/ | grep 6.0 -check "oryx-install-nodejs-12.22.11" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-nodejs-20.11.0" oryx prep --skip-detection --platforms-and-versions nodejs=20.11.0 +check "nodejs-20.11.0-installed-by-oryx" ls /opt/nodejs/ | grep 20.11.0 -check "oryx-install-php-7.3.25" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx" ls /opt/php/ | grep 7.3.25 - -check "oryx-install-java-12.0.2" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.1.22" oryx prep --skip-detection --platforms-and-versions php=8.1.22 +check "php-8.1.22-installed-by-oryx" ls /opt/php/ | grep 8.1.22 # Replicates Oryx's behavior for universal image mkdir -p /opt/oryx -echo "vso-focal" >> /opt/oryx/.imagetype +echo "vso-bookworm" >> /opt/oryx/.imagetype mkdir -p /opt/dotnet/lts cp -R /usr/share/dotnet/dotnet /opt/dotnet/lts @@ -31,17 +28,14 @@ cp -R /usr/share/dotnet/LICENSE.txt /opt/dotnet/lts cp -R /usr/share/dotnet/ThirdPartyNotices.txt /opt/dotnet/lts # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1-universal" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx-universal" ls /opt/dotnet/ | grep 2.1 - -check "oryx-install-nodejs-12.22.11-universal" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx-universal" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-dotnet-6.0" oryx prep --skip-detection --platforms-and-versions dotnet=6.0.23 +check "dotnet-6-installed-by-oryx" ls /opt/dotnet/ | grep 6.0 -check "oryx-install-php-7.3.25-universal" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx-universal" ls /opt/php/ | grep 7.3.25 +check "oryx-install-nodejs-20.11.0" oryx prep --skip-detection --platforms-and-versions nodejs=20.11.0 +check "nodejs-20.11.0-installed-by-oryx" ls /opt/nodejs/ | grep 20.11.0 -check "oryx-install-java-12.0.2-universal" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx-universal" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.1.22" oryx prep --skip-detection --platforms-and-versions php=8.1.22 +check "php-8.1.22-installed-by-oryx" ls /opt/php/ | grep 8.1.22 # Report result reportResults diff --git a/test/oryx/install_prev_dotnet_and_oryx.sh b/test/oryx/install_prev_dotnet_and_oryx.sh index da0a9162d..48cd98504 100644 --- a/test/oryx/install_prev_dotnet_and_oryx.sh +++ b/test/oryx/install_prev_dotnet_and_oryx.sh @@ -9,21 +9,18 @@ check "Oryx version" oryx --version check "Dotnet is not removed if it is not installed by the Oryx Feature" dotnet --version # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx" ls /opt/dotnet/ | grep 2.1 +check "oryx-install-dotnet-9.0" oryx prep --skip-detection --platforms-and-versions dotnet=9.0.1 +check "dotnet-9-installed-by-oryx" ls /opt/dotnet/ | grep 9.0 -check "oryx-install-nodejs-12.22.11" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-nodejs-22.9.0" oryx prep --skip-detection --platforms-and-versions nodejs=22.9.0 +check "nodejs-22.9.0-installed-by-oryx" ls /opt/nodejs/ | grep 22.9.0 -check "oryx-install-php-7.3.25" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx" ls /opt/php/ | grep 7.3.25 - -check "oryx-install-java-12.0.2" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.3.20" oryx prep --skip-detection --platforms-and-versions php=8.3.20 +check "php-8.3.20-installed-by-oryx" ls /opt/php/ | grep 8.3.20 # Replicates Oryx's behavior for universal image mkdir -p /opt/oryx -echo "vso-focal" >> /opt/oryx/.imagetype +echo "vso-bookworm" >> /opt/oryx/.imagetype mkdir -p /opt/dotnet/lts cp -R /usr/share/dotnet/dotnet /opt/dotnet/lts @@ -31,17 +28,14 @@ cp -R /usr/share/dotnet/LICENSE.txt /opt/dotnet/lts cp -R /usr/share/dotnet/ThirdPartyNotices.txt /opt/dotnet/lts # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1-universal" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx-universal" ls /opt/dotnet/ | grep 2.1 - -check "oryx-install-nodejs-12.22.11-universal" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx-universal" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-dotnet-9.0" oryx prep --skip-detection --platforms-and-versions dotnet=9.0.1 +check "dotnet-9-installed-by-oryx" ls /opt/dotnet/ | grep 9.0 -check "oryx-install-php-7.3.25-universal" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx-universal" ls /opt/php/ | grep 7.3.25 +check "oryx-install-nodejs-22.9.0" oryx prep --skip-detection --platforms-and-versions nodejs=22.9.0 +check "nodejs-22.9.0-installed-by-oryx" ls /opt/nodejs/ | grep 22.9.0 -check "oryx-install-java-12.0.2-universal" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx-universal" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.3.20" oryx prep --skip-detection --platforms-and-versions php=8.3.20 +check "php-8.3.20-installed-by-oryx" ls /opt/php/ | grep 8.3.20 # Report result reportResults diff --git a/test/oryx/scenarios.json b/test/oryx/scenarios.json index 4b3643590..88ec5e2c2 100644 --- a/test/oryx/scenarios.json +++ b/test/oryx/scenarios.json @@ -1,11 +1,11 @@ { "install_dotnet_and_oryx": { - "image": "ubuntu:noble", + "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { "dotnet": { - "version": "8.0", - "dotnetRuntimeVersions": "7.0", - "aspNetCoreRuntimeVersions": "7.0" + "version": "10.0", + "dotnetRuntimeVersions": "9.0", + "aspNetCoreRuntimeVersions": "9.0" }, "oryx": {} } @@ -23,7 +23,7 @@ "image": "ubuntu:noble", "features": { "dotnet": { - "version": "6.0" + "version": "8.0" }, "oryx": {} } diff --git a/test/oryx/test.sh b/test/oryx/test.sh index c5d606783..1df438e16 100755 --- a/test/oryx/test.sh +++ b/test/oryx/test.sh @@ -10,17 +10,14 @@ check "ORYX_SDK_STORAGE_BASE_URL" echo $ORYX_SDK_STORAGE_BASE_URL check "ENABLE_DYNAMIC_INSTALL" echo $ENABLE_DYNAMIC_INSTALL # Install platforms with oryx build tool -check "oryx-install-dotnet-2.1" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.30 -check "dotnet-2-installed-by-oryx" ls /opt/dotnet/ | grep 2.1 +check "oryx-install-dotnet-8.0" oryx prep --skip-detection --platforms-and-versions dotnet=8.0.23 +check "dotnet-2-installed-by-oryx" ls /opt/dotnet/ | grep 8.0 -check "oryx-install-nodejs-12.22.11" oryx prep --skip-detection --platforms-and-versions nodejs=12.22.11 -check "nodejs-12.22.11-installed-by-oryx" ls /opt/nodejs/ | grep 12.22.11 +check "oryx-install-nodejs-20.11.0" oryx prep --skip-detection --platforms-and-versions nodejs=20.11.0 +check "nodejs-20.11.0-installed-by-oryx" ls /opt/nodejs/ | grep 20.11.0 -check "oryx-install-php-7.3.25" oryx prep --skip-detection --platforms-and-versions php=7.3.25 -check "php-7.3.25-installed-by-oryx" ls /opt/php/ | grep 7.3.25 - -check "oryx-install-java-12.0.2" oryx prep --skip-detection --platforms-and-versions java=12.0.2 -check "java-12.0.2-installed-by-oryx" ls /opt/java/ | grep 12.0.2 +check "oryx-install-php-8.1.30" oryx prep --skip-detection --platforms-and-versions php=8.1.30 +check "php-8.1.30-installed-by-oryx" ls /opt/php/ | grep 8.1.30 # Report result reportResults \ No newline at end of file From 5e587d1725f0d274bc09a2bd33490b4236f1619c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:29:34 +0000 Subject: [PATCH 33/78] Bump azohra/shell-linter from 0.6.0 to 0.8.0 (#1593) Bumps [azohra/shell-linter](https://github.com/azohra/shell-linter) from 0.6.0 to 0.8.0. - [Release notes](https://github.com/azohra/shell-linter/releases) - [Commits](https://github.com/azohra/shell-linter/compare/v0.6.0...v0.8.0) --- updated-dependencies: - dependency-name: azohra/shell-linter dependency-version: 0.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linter-automated.yaml | 2 +- .github/workflows/linter-manual.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linter-automated.yaml b/.github/workflows/linter-automated.yaml index 46cbd085a..55d6f40b8 100644 --- a/.github/workflows/linter-automated.yaml +++ b/.github/workflows/linter-automated.yaml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - name: Shell Linter - uses: azohra/shell-linter@v0.6.0 + uses: azohra/shell-linter@v0.8.0 with: path: "src/**/*.sh" severity: "error" # [style, info, warning, error] diff --git a/.github/workflows/linter-manual.yaml b/.github/workflows/linter-manual.yaml index 019724d35..dbc3794b7 100644 --- a/.github/workflows/linter-manual.yaml +++ b/.github/workflows/linter-manual.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - name: Shell Linter - uses: azohra/shell-linter@v0.6.0 + uses: azohra/shell-linter@v0.8.0 with: path: ${{ github.event.inputs.path }} severity: ${{ github.event.inputs.severity }} From 1e831fbf2713fa79517b994e9895f5b5be3a4a14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:29:55 +0000 Subject: [PATCH 34/78] Bump actions/checkout from 4 to 6 (#1592) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abdurrahmaan Iqbal --- .github/workflows/docker-in-docker-stress-test.yaml | 4 ++-- .github/workflows/linter-automated.yaml | 2 +- .github/workflows/linter-manual.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/test-all.yaml | 6 +++--- .github/workflows/test-manual.yaml | 2 +- .github/workflows/test-pr-arm64.yaml | 4 ++-- .github/workflows/test-pr.yaml | 4 ++-- .github/workflows/update-aws-cli-completer-scripts.yml | 2 +- .github/workflows/update-documentation.yml | 2 +- .github/workflows/update-dotnet-install-script.yml | 2 +- .github/workflows/validate-metadata-files.yml | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker-in-docker-stress-test.yaml b/.github/workflows/docker-in-docker-stress-test.yaml index 1c9410a88..99470e558 100644 --- a/.github/workflows/docker-in-docker-stress-test.yaml +++ b/.github/workflows/docker-in-docker-stress-test.yaml @@ -13,7 +13,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -28,7 +28,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/linter-automated.yaml b/.github/workflows/linter-automated.yaml index 55d6f40b8..234f7e726 100644 --- a/.github/workflows/linter-automated.yaml +++ b/.github/workflows/linter-automated.yaml @@ -9,7 +9,7 @@ jobs: shellchecker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Shell Linter uses: azohra/shell-linter@v0.8.0 diff --git a/.github/workflows/linter-manual.yaml b/.github/workflows/linter-manual.yaml index dbc3794b7..5d4081f5a 100644 --- a/.github/workflows/linter-manual.yaml +++ b/.github/workflows/linter-manual.yaml @@ -15,7 +15,7 @@ jobs: shellchecker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Shell Linter uses: azohra/shell-linter@v0.8.0 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6890f5ae5..ab3c0a34b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ jobs: packages: write contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Publish" uses: devcontainers/action@v1 diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index ea4fe8728..4b73845f7 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -52,7 +52,7 @@ jobs: "mcr.microsoft.com/devcontainers/base:noble" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -96,7 +96,7 @@ jobs: "nix", ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -108,7 +108,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/test-manual.yaml b/.github/workflows/test-manual.yaml index 10d099b25..cfee816c2 100644 --- a/.github/workflows/test-manual.yaml +++ b/.github/workflows/test-manual.yaml @@ -19,7 +19,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index d82ec3409..b2adfc7ae 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -42,7 +42,7 @@ jobs: "mcr.microsoft.com/devcontainers/base:noble" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -58,7 +58,7 @@ jobs: matrix: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 645d38109..09478e51d 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -68,7 +68,7 @@ jobs: - features: docker-outside-of-docker baseImage: mcr.microsoft.com/devcontainers/base:debian steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -84,7 +84,7 @@ jobs: matrix: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/update-aws-cli-completer-scripts.yml b/.github/workflows/update-aws-cli-completer-scripts.yml index 41d67189f..d0c119d09 100644 --- a/.github/workflows/update-aws-cli-completer-scripts.yml +++ b/.github/workflows/update-aws-cli-completer-scripts.yml @@ -12,7 +12,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run fetch-latest-completer-scripts.sh run: src/aws-cli/scripts/fetch-latest-completer-scripts.sh diff --git a/.github/workflows/update-documentation.yml b/.github/workflows/update-documentation.yml index d74d970d5..96c12176b 100644 --- a/.github/workflows/update-documentation.yml +++ b/.github/workflows/update-documentation.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write if: "github.ref == 'refs/heads/main'" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Generate Documentation uses: devcontainers/action@v1 diff --git a/.github/workflows/update-dotnet-install-script.yml b/.github/workflows/update-dotnet-install-script.yml index 604f6880b..19aab95e2 100644 --- a/.github/workflows/update-dotnet-install-script.yml +++ b/.github/workflows/update-dotnet-install-script.yml @@ -12,7 +12,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run fetch-latest-dotnet-install.sh run: src/dotnet/scripts/fetch-latest-dotnet-install.sh diff --git a/.github/workflows/validate-metadata-files.yml b/.github/workflows/validate-metadata-files.yml index 863418e93..dfb5b25f0 100644 --- a/.github/workflows/validate-metadata-files.yml +++ b/.github/workflows/validate-metadata-files.yml @@ -7,7 +7,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: "Validate devcontainer-feature.json files" uses: devcontainers/action@v1 From a2fd449926079efa1c270c4c64473143499292af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:30:53 +0000 Subject: [PATCH 35/78] Bump dorny/paths-filter from 3 to 4 (#1604) Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 3 to 4. - [Release notes](https://github.com/dorny/paths-filter/releases) - [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md) - [Commits](https://github.com/dorny/paths-filter/compare/v3...v4) --- updated-dependencies: - dependency-name: dorny/paths-filter dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abdurrahmaan Iqbal --- .github/workflows/test-pr-arm64.yaml | 2 +- .github/workflows/test-pr.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index b2adfc7ae..ea745345d 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -15,7 +15,7 @@ jobs: outputs: features: ${{ steps.filter.outputs.changes }} steps: - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v4 id: filter with: # NOTE: To extend this workflow to other features, add filter entries below diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 09478e51d..2747ec582 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -8,7 +8,7 @@ jobs: outputs: features: ${{ steps.filter.outputs.changes }} steps: - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v4 id: filter with: filters: | From d3971c3960448afdf4f5f693eacf1cd36f804bda Mon Sep 17 00:00:00 2001 From: sireeshajonnalagadda Date: Thu, 26 Mar 2026 17:57:48 +0530 Subject: [PATCH 36/78] Add rootless Docker support and update documentation (#1549) * Add rootless Docker support and update documentation * Ensure parent directory exists before creating source socket * fixing errors * Resolving conflicts * fix(docker-outside-of-docker): update default for installDockerComposeSwitch and remove obsolete ROOTLESS_DOCKER.md documentation * Bump version to 1.9.0 * Delete test/docker-outside-of-docker/custom_rootless_socket_path.sh similar scenario already exists * Delete test/docker-outside-of-docker/xdg_runtime_dir_socket.sh * streamline Docker CLI and Buildx installation process * fixing syntax errors * fixing syntax errors * Enhance Docker installation script for backward compatibility and update root Docker socket detection --------- Co-authored-by: Kaniska --- src/docker-outside-of-docker/README.md | 25 +++++ .../devcontainer-feature.json | 99 ++++++++++--------- src/docker-outside-of-docker/install.sh | 23 +++-- .../docker_dash_compose_v1.sh | 1 + .../root_docker_socket.sh | 11 +++ .../rootless_docker_socket.sh | 27 +++++ test/docker-outside-of-docker/scenarios.json | 28 ++++++ 7 files changed, 161 insertions(+), 53 deletions(-) create mode 100644 test/docker-outside-of-docker/root_docker_socket.sh create mode 100644 test/docker-outside-of-docker/rootless_docker_socket.sh diff --git a/src/docker-outside-of-docker/README.md b/src/docker-outside-of-docker/README.md index e4fe0c446..3b37028e2 100644 --- a/src/docker-outside-of-docker/README.md +++ b/src/docker-outside-of-docker/README.md @@ -23,6 +23,7 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv | dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none or latest) | string | latest | | installDockerBuildx | Install Docker Buildx | boolean | true | | installDockerComposeSwitch | Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter. | boolean | true | +| socketPath | Path where the Docker socket is mounted inside the container. For rootless Docker, override the mount in devcontainer.json to map your host socket to this path. | string | /var/run/docker-host.sock | ## Customizations @@ -36,6 +37,30 @@ Re-use the host docker socket, adding the Docker CLI to a container. Feature inv - The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, for example. - This approach does not currently enable bind mounting the workspace folder by default, and cannot support folders outside of the workspace folder. Consider whether the [Docker-in-Docker Feature](../docker-in-docker) would better meet your needs given it does not have this limitation. +## Rootless Docker Support + +By default, this feature expects the Docker socket at `/var/run/docker.sock` on the host, which works for standard (root) Docker installations. For **rootless Docker** setups where the socket is located at `/run/user/$UID/docker.sock` or `$XDG_RUNTIME_DIR/docker.sock`, you need to override the mount in your `devcontainer.json`: + +```json +{ + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} + }, + "mounts": [ + { + "source": "/run/user/1000/docker.sock", + "target": "/var/run/docker-host.sock", + "type": "bind" + } + ] +} +``` + +**Notes:** +- Replace `1000` with your actual user ID (run `id -u` to find it) +- The feature will automatically detect the socket at `/var/run/docker-host.sock` +- Your custom mount will override the feature's default mount + ## Supporting bind mounts from the workspace folder A common question that comes up is how you can use `bind` mounts from the Docker CLI from within the a dev container using this Feature (e.g. via `-v`). If you cannot use the [Docker-in-Docker Feature](../docker-in-docker), the only way to work around this is to use the **host**'s folder paths instead of the container's paths. There are 2 ways to do this diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index f2e57bd43..d98b8021c 100644 --- a/src/docker-outside-of-docker/devcontainer-feature.json +++ b/src/docker-outside-of-docker/devcontainer-feature.json @@ -1,53 +1,58 @@ { - "id": "docker-outside-of-docker", - "version": "1.9.0", - "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.", - "options": { - "version": { - "type": "string", - "proposals": [ - "latest", - "none", - "20.10" - ], - "default": "latest", - "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)" - }, - "moby": { - "type": "boolean", - "default": true, - "description": "Install OSS Moby build instead of Docker CE" - }, - "mobyBuildxVersion": { - "type": "string", - "default": "latest", - "description": "Install a specific version of moby-buildx when using Moby" - }, - "dockerDashComposeVersion": { - "type": "string", - "enum": [ - "none", - "latest", - "v1", - "v2" - ], - "default": "latest", - "description": "Compose version to use for docker-compose (v1 or v2 or none or latest)" - }, - "installDockerBuildx": { - "type": "boolean", - "default": true, - "description": "Install Docker Buildx" + "id": "docker-outside-of-docker", + "version": "1.9.1", + "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.", + "options": { + "version": { + "type": "string", + "proposals": [ + "latest", + "none", + "20.10" + ], + "default": "latest", + "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)" + }, + "moby": { + "type": "boolean", + "default": true, + "description": "Install OSS Moby build instead of Docker CE" + }, + "mobyBuildxVersion": { + "type": "string", + "default": "latest", + "description": "Install a specific version of moby-buildx when using Moby" + }, + "dockerDashComposeVersion": { + "type": "string", + "enum": [ + "none", + "latest", + "v1", + "v2" + ], + "default": "latest", + "description": "Compose version to use for docker-compose (v1 or v2 or none or latest)" + }, + "installDockerBuildx": { + "type": "boolean", + "default": true, + "description": "Install Docker Buildx" + }, + "installDockerComposeSwitch": { + "type": "boolean", + "default": false, + "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." + }, + "socketPath": { + "type": "string", + "default": "/var/run/docker-host.sock", + "description": "Path where the Docker socket is mounted inside the container. For rootless Docker, override the mount in devcontainer.json to map your host socket to this path." + } }, - "installDockerComposeSwitch": { - "type": "boolean", - "default": true, - "description": "Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter." - } - }, "entrypoint": "/usr/local/share/docker-init.sh", "customizations": { "vscode": { diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index 6e26b13ac..4799a4d59 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -13,7 +13,8 @@ MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}" DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"latest"}" # v1 or v2 or none or latest ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}" -SOURCE_SOCKET="${SOURCE_SOCKET:-"/var/run/docker-host.sock"}" +SOCKET_PATH="${SOCKETPATH:-"/var/run/docker-host.sock"}" # From feature option +SOURCE_SOCKET="${SOURCE_SOCKET:-"${SOCKET_PATH}"}" TARGET_SOCKET="${TARGET_SOCKET:-"/var/run/docker.sock"}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" INSTALL_DOCKER_BUILDX="${INSTALLDOCKERBUILDX:-"true"}" @@ -318,23 +319,31 @@ else buildx=(moby-buildx${buildx_version_suffix}) fi apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" || { err "It seems packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-24.04')." ; exit 1 ; } - apt-get -y install --no-install-recommends moby-compose || echo "(*) Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "v1" ]; then + apt-get -y install --no-install-recommends moby-compose || echo "(*) Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + fi else buildx=() if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then buildx=(docker-buildx-plugin) fi - apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" docker-compose-plugin + #install cli + buildx first + apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" + + # Backward compatibility: Older Docker CE versions bundled buildx with CLI + # Modern versions have separate packages, but this ensures consistent behavior buildx_path="/usr/libexec/docker/cli-plugins/docker-buildx" - # Older versions of Docker CE installs buildx as part of the CLI package if [ "${INSTALL_DOCKER_BUILDX}" = "false" ] && [ -f "${buildx_path}" ]; then - echo "(*) Removing docker-buildx installed from docker-ce-cli since installDockerBuildx is disabled..." + echo "(*) Removing docker-buildx (bundled in older Docker CE) since installDockerBuildx is disabled..." rm -f "${buildx_path}" fi + + if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "v1" ]; then + apt-get -y install --no-install-recommends docker-compose-plugin + fi fi unset buildx buildx_path fi - # If 'docker-compose' command is to be included if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then case "${architecture}" in @@ -438,6 +447,8 @@ echo "docker-init doesn't exist, adding..." # By default, make the source and target sockets the same if [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ]; then + # Create parent directory if it doesn't exist + mkdir -p "$(dirname "${SOURCE_SOCKET}")" touch "${SOURCE_SOCKET}" ln -s "${SOURCE_SOCKET}" "${TARGET_SOCKET}" fi diff --git a/test/docker-outside-of-docker/docker_dash_compose_v1.sh b/test/docker-outside-of-docker/docker_dash_compose_v1.sh index 4ae7a9e02..33ae319f4 100755 --- a/test/docker-outside-of-docker/docker_dash_compose_v1.sh +++ b/test/docker-outside-of-docker/docker_dash_compose_v1.sh @@ -7,6 +7,7 @@ source dev-container-features-test-lib # Definition specific tests check "docker-compose" bash -c "docker-compose --version | grep -E '1.[0-9]+.[0-9]+'" +check "no docker compose plugin" bash -c "if command -v docker >/dev/null 2>&1; then ! docker compose version >/dev/null 2>&1; else true; fi" # Report result reportResults diff --git a/test/docker-outside-of-docker/root_docker_socket.sh b/test/docker-outside-of-docker/root_docker_socket.sh new file mode 100644 index 000000000..c40d8a0ce --- /dev/null +++ b/test/docker-outside-of-docker/root_docker_socket.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Test script to assert root Docker socket usage + +if [ ! -S "/var/run/docker-host.sock" ]; then + echo "ERROR: Root Docker socket not found" + exit 1 +fi + +echo "Root Docker detected" +export DOCKER_HOST="unix:///var/run/docker-host.sock" +docker --version \ No newline at end of file diff --git a/test/docker-outside-of-docker/rootless_docker_socket.sh b/test/docker-outside-of-docker/rootless_docker_socket.sh new file mode 100644 index 000000000..004554e9a --- /dev/null +++ b/test/docker-outside-of-docker/rootless_docker_socket.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +source dev-container-features-test-lib + +echo "=== Rootless Docker Socket Configuration Test ===" + +# Test the custom rootless socket path +EXPECTED_SOCKET="/var/run/docker-rootless.sock" + +# Check if the configured rootless socket exists and is accessible +check "rootless-socket-exists" test -S "$EXPECTED_SOCKET" +check "rootless-socket-readable" test -r "$EXPECTED_SOCKET" + +# Verify Docker functionality using the rootless socket +export DOCKER_HOST="unix://$EXPECTED_SOCKET" +check "docker-functional-rootless" docker ps >/dev/null + +# Test basic Docker operations with rootless configuration +check "docker-version-rootless" docker version --format '{{.Client.Version}}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' >/dev/null +check "docker-info-rootless" docker info >/dev/null + +# Demonstrate that customers can configure custom socket paths +echo "Configured rootless socket path: $EXPECTED_SOCKET" +echo "Docker host: $DOCKER_HOST" + +reportResults \ No newline at end of file diff --git a/test/docker-outside-of-docker/scenarios.json b/test/docker-outside-of-docker/scenarios.json index 3a49d594e..61b94f3d6 100644 --- a/test/docker-outside-of-docker/scenarios.json +++ b/test/docker-outside-of-docker/scenarios.json @@ -180,6 +180,32 @@ "moby": false } } + }, + "rootless_docker_socket": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": false, + "socketPath": "/var/run/docker-rootless.sock" + } + }, + "mounts": [ + { + "source": "/var/run/docker.sock", + "target": "/var/run/docker-rootless.sock", + "type": "bind" + } + ], + "containerUser": "vscode" + }, + "root_docker_socket": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": false + } + }, + "containerUser": "vscode" }, "docker_dash_compose_latest_moby": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", @@ -202,3 +228,5 @@ "containerUser": "vscode" } } + + \ No newline at end of file From 07f4101951e61c61fd1c8cfd00250af3716a6c2a Mon Sep 17 00:00:00 2001 From: Kaniska Date: Fri, 27 Mar 2026 21:56:11 +0530 Subject: [PATCH 37/78] Fix Java feature: fallback to LTS when latest feature release is unavailable (#1614) --- src/java/devcontainer-feature.json | 2 +- src/java/install.sh | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/java/devcontainer-feature.json b/src/java/devcontainer-feature.json index 9591c3592..c82718a49 100644 --- a/src/java/devcontainer-feature.json +++ b/src/java/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "java", - "version": "1.7.2", + "version": "1.8.0", "name": "Java (via SDKMAN!)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/java", "description": "Installs Java, SDKMAN! (if not installed), and needed dependencies.", diff --git a/src/java/install.sh b/src/java/install.sh index cd93eacf6..988460307 100644 --- a/src/java/install.sh +++ b/src/java/install.sh @@ -265,8 +265,19 @@ sdk_install() { set -e fi if [ -z "${requested_version}" ] || ! echo "${version_list}" | grep "^${requested_version//./\\.}$" > /dev/null 2>&1; then - echo -e "Version $2 not found. Available versions:\n${version_list}" >&2 - exit 1 + # Fallback to LTS if "latest" was requested and not found (java only) + if [ "$2" = "latest" ] && [ "${install_type}" = "java" ]; then + echo "Latest version not found in SDKMAN. Falling back to LTS..." + find_version_list "$prefix" "$suffix" "$install_type" "true" version_list "lts" + requested_version="$(echo "${version_list}" | head -n 1)" + if [ -z "${requested_version}" ] || ! echo "${version_list}" | grep "^${requested_version//./\\.}$" > /dev/null 2>&1; then + echo -e "Version $2 (and LTS fallback) not found. Available versions:\n${version_list}" >&2 + exit 1 + fi + else + echo -e "Version $2 not found. Available versions:\n${version_list}" >&2 + exit 1 + fi fi fi if [ "${set_as_default}" = "true" ]; then From ceee60f6d5ff1c7834a48d417e9aca055f6aa29f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:52:07 +0100 Subject: [PATCH 38/78] Fix failing nix version test scenario by updating pinned version to 2.18 (#1610) fix: update nix version from 2.10 to 2.18 in test scenario Agent-Logs-Url: https://github.com/devcontainers/features/sessions/b026e9e9-065b-47f1-aa99-ff8fe8a4ceb0 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> Co-authored-by: Kaniska --- test/nix/scenarios.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nix/scenarios.json b/test/nix/scenarios.json index dac5624f1..38eed0268 100644 --- a/test/nix/scenarios.json +++ b/test/nix/scenarios.json @@ -14,7 +14,7 @@ "features": { "nix": { "multiUser": false, - "version": "2.10" + "version": "2.18" } } }, From 3df3aed1e7bfcdd91e97fa2d5d7cbefff1dde4cf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:52:36 +0100 Subject: [PATCH 39/78] fix: update azure-cli test scenario base image from dev-10.0-preview-trixie to base:trixie (#1611) * fix: update azure-cli test scenario base image from dev-10.0-preview-trixie to 10.0 Agent-Logs-Url: https://github.com/devcontainers/features/sessions/78a9b225-cd77-4447-9ee4-4de4b2546665 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * fix: use base:trixie image for trixie-specific test scenario Agent-Logs-Url: https://github.com/devcontainers/features/sessions/5e6ed3a3-e87e-449f-ac46-ccc170990a0b Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> Co-authored-by: Kaniska --- test/azure-cli/scenarios.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/azure-cli/scenarios.json b/test/azure-cli/scenarios.json index 4c12034b5..1ba173de9 100644 --- a/test/azure-cli/scenarios.json +++ b/test/azure-cli/scenarios.json @@ -89,7 +89,7 @@ } }, "install_azcli_dotnet_dockerindocker_trixie": { - "image": "mcr.microsoft.com/devcontainers/dotnet:dev-10.0-preview-trixie", + "image": "mcr.microsoft.com/devcontainers/base:trixie", "user": "vscode", "features": { "azure-cli": { From a1a09985015999d98717b82971b51c40665b04db Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:37:52 +0100 Subject: [PATCH 40/78] Handle PowerShell RC versions in preview install path (#1608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix powershell preview/RC version installation failure - Add -rc. version checks alongside preview checks in routing conditions so RC versions go directly to GitHub install instead of apt/dnf - Update find_preview_version_from_git_tags() to match -rc.X git tags - Fix typo googlegit_cmd_name -> git_cmd_name - Update preview test scripts to match both preview and rc.X patterns Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> Agent-Logs-Url: https://github.com/devcontainers/features/sessions/03fd0565-a230-4bd9-80f7-92b8ce74a1bb * Version bump: powershell feature 2.0.1 โ†’ 2.0.2 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> Agent-Logs-Url: https://github.com/devcontainers/features/sessions/98f1f7bd-4d93-4641-85d8-287b218c1eb8 * Remove ubuntu:focal and debian:11 from arm64 test matrix PowerShell 7.6.0 requires GLIBC_2.33+ which is unavailable on these older platforms. ubuntu:focal is already obsolete and debian:11 reaches EOL in Aug 2026. Agent-Logs-Url: https://github.com/devcontainers/features/sessions/6a803744-ae1d-4c84-8e04-5bb2c0df1523 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * Add comment explaining ubuntu:focal and debian:11 removal from arm64 matrix Agent-Logs-Url: https://github.com/devcontainers/features/sessions/071d8550-7b74-4436-91de-5933b6711bc3 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> Co-authored-by: Kaniska --- .github/workflows/test-pr-arm64.yaml | 5 ++-- src/powershell/devcontainer-feature.json | 2 +- src/powershell/install.sh | 23 +++++++++++-------- test/powershell/powershell_preview_version.sh | 2 +- .../powershell_preview_version_almalinux.sh | 2 +- .../powershell_preview_version_debian.sh | 2 +- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index ea745345d..113ce39b5 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -31,11 +31,12 @@ jobs: strategy: matrix: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} + # NOTE: ubuntu:focal and debian:11 are excluded because they ship + # GLIBC 2.31, but PowerShell >= 7.6.0 requires GLIBC 2.33+ on arm64. + # ubuntu:focal reached EOL Apr 2025; debian:11 reaches EOL Aug 2026. baseImage: [ - "ubuntu:focal", "ubuntu:jammy", - "debian:11", "debian:12", "mcr.microsoft.com/devcontainers/base:ubuntu", "mcr.microsoft.com/devcontainers/base:debian", diff --git a/src/powershell/devcontainer-feature.json b/src/powershell/devcontainer-feature.json index 3b500608d..009f4c238 100644 --- a/src/powershell/devcontainer-feature.json +++ b/src/powershell/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "powershell", - "version": "2.0.1", + "version": "2.0.2", "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 40308c32e..cabd2cbb4 100755 --- a/src/powershell/install.sh +++ b/src/powershell/install.sh @@ -126,7 +126,7 @@ find_preview_version_from_git_tags() { local requested_version=${!variable_name} local repository_url=$2 - if [ -z "${googlegit_cmd_name}" ]; then + if [ -z "${git_cmd_name}" ]; then if type git > /dev/null 2>&1; then git_cmd_name="git" else @@ -135,22 +135,22 @@ find_preview_version_from_git_tags() { fi fi - # Fetch tags from remote repository + # Fetch tags from remote repository (match both -preview.X and -rc.X tags) 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) + tags=$(git ls-remote --tags "${repository_url}" 2>/dev/null | grep -oP 'refs/tags/v\K[0-9]+\.[0-9]+\.[0-9]+-(preview|rc)\.[0-9]+' | sort -V) if [ -z "${tags}" ]; then - echo "No preview tags found in repository." + echo "No preview/rc tags found in repository." return 1 fi local version="" if [ "${requested_version}" = "preview" ] || [ "${requested_version}" = "latest" ]; then - # Get the latest preview version + # Get the latest preview/rc 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 + # Partial version provided (e.g., "7.6"), find latest preview/rc 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 @@ -161,6 +161,11 @@ find_preview_version_from_git_tags() { if echo "${tags}" | grep -q "^${requested_version}$"; then version="${requested_version}" fi + elif [[ "${requested_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then + # Exact RC version provided, verify it exists + if echo "${tags}" | grep -q "^${requested_version}$"; then + version="${requested_version}" + fi fi if [ -z "${version}" ]; then @@ -382,7 +387,7 @@ install_using_github() { fi pwsh_url="https://github.com/PowerShell/PowerShell" # Check if we need to find a preview version or stable version - if [[ "${POWERSHELL_VERSION}" == *"preview"* ]] || [ "${POWERSHELL_VERSION}" = "preview" ]; then + 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}" else @@ -446,9 +451,9 @@ 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}"* ]] && [[ "${POWERSHELL_VERSION}" != *"preview"* ]]; then + if [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_UBUNTU}"* ]] && [[ "${POWERSHELL_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]] && [[ "${POWERSHELL_VERSION}" != *"preview"* ]] && [[ "${POWERSHELL_VERSION}" != *"-rc."* ]]; then install_using_apt || use_github="true" - elif [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_ALMALINUX}"* ]] && [[ "${POWERSHELL_VERSION}" != *"preview"* ]]; then + elif [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${POWERSHELL_ARCHIVE_ARCHITECTURES_ALMALINUX}"* ]] && [[ "${POWERSHELL_VERSION}" != *"preview"* ]] && [[ "${POWERSHELL_VERSION}" != *"-rc."* ]]; then install_using_dnf && install_powershell_dnf || use_github="true" else use_github="true" diff --git a/test/powershell/powershell_preview_version.sh b/test/powershell/powershell_preview_version.sh index 7e49ded9e..91238e794 100755 --- a/test/powershell/powershell_preview_version.sh +++ b/test/powershell/powershell_preview_version.sh @@ -7,7 +7,7 @@ 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 version is preview" bash -c "pwsh --version | grep -iE 'preview|rc\.[0-9]+'" check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" # Report result diff --git a/test/powershell/powershell_preview_version_almalinux.sh b/test/powershell/powershell_preview_version_almalinux.sh index 3a8128209..806dbf324 100755 --- a/test/powershell/powershell_preview_version_almalinux.sh +++ b/test/powershell/powershell_preview_version_almalinux.sh @@ -7,7 +7,7 @@ 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 version is preview" bash -c "pwsh --version | grep -iE 'preview|rc\.[0-9]+'" check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" # Report result diff --git a/test/powershell/powershell_preview_version_debian.sh b/test/powershell/powershell_preview_version_debian.sh index 316017cf7..1d8ba4e36 100755 --- a/test/powershell/powershell_preview_version_debian.sh +++ b/test/powershell/powershell_preview_version_debian.sh @@ -7,7 +7,7 @@ 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 version is preview" bash -c "pwsh --version | grep -iE 'preview|rc\.[0-9]+'" check "pwsh can execute basic command" bash -c "pwsh -Command 'Write-Output Hello'" # Report result From 9f086c01b93e85024dcafae432aa0a3044d02652 Mon Sep 17 00:00:00 2001 From: Mathiyarasy <157102811+Mathiyarasy@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:03:06 +0530 Subject: [PATCH 41/78] [Desktop-lite] desktop-init.sh drops entrypoint args due to heredoc variable expansion (#1621) * update script * update minor version --- src/desktop-lite/devcontainer-feature.json | 2 +- src/desktop-lite/install.sh | 4 ++-- test/desktop-lite/scenarios.json | 11 ++++++++--- .../test_desktop_init_exec_passthrough.sh | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 test/desktop-lite/test_desktop_init_exec_passthrough.sh diff --git a/src/desktop-lite/devcontainer-feature.json b/src/desktop-lite/devcontainer-feature.json index ae10c9977..b87e2bc02 100644 --- a/src/desktop-lite/devcontainer-feature.json +++ b/src/desktop-lite/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "desktop-lite", - "version": "1.2.8", + "version": "1.2.9", "name": "Light-weight Desktop", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/desktop-lite", "description": "Adds a lightweight Fluxbox based desktop to the container that can be accessed using a VNC viewer or the web. GUI-based commands executed from the built-in VS code terminal will open on the desktop automatically.", diff --git a/src/desktop-lite/install.sh b/src/desktop-lite/install.sh index 4575cc4f9..822818723 100755 --- a/src/desktop-lite/install.sh +++ b/src/desktop-lite/install.sh @@ -409,9 +409,9 @@ else fi # Run whatever was passed in -if [ -n "$1" ]; then +if [ -n "\$1" ]; then log "Executing \"\$@\"." - exec "$@" + exec "\$@" else log "No command provided to execute." fi diff --git a/test/desktop-lite/scenarios.json b/test/desktop-lite/scenarios.json index 4ee429dd5..f8719acc6 100644 --- a/test/desktop-lite/scenarios.json +++ b/test/desktop-lite/scenarios.json @@ -12,13 +12,12 @@ "noVncVersion": "1.2.0" } } - }, + }, "test_vnc_resolution_as_container_env_var": { "image": "ubuntu:noble", "features": { "desktop-lite": {} - } - , + }, "containerEnv": { "VNC_RESOLUTION": "1920x1080x32" }, @@ -45,5 +44,11 @@ "features": { "desktop-lite": {} } + }, + "test_desktop_init_exec_passthrough": { + "image": "ubuntu:noble", + "features": { + "desktop-lite": {} + } } } \ No newline at end of file diff --git a/test/desktop-lite/test_desktop_init_exec_passthrough.sh b/test/desktop-lite/test_desktop_init_exec_passthrough.sh new file mode 100644 index 000000000..27d224ca3 --- /dev/null +++ b/test/desktop-lite/test_desktop_init_exec_passthrough.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Verify that desktop-init.sh correctly passes through commands. +# Previously, the heredoc in install.sh did not escape $1 and $@, causing them to expand +# to empty strings at install time, so any command passed to desktop-init.sh was silently ignored. + +check "command is passed through and executed" \ + bash -c "result=\$(/usr/local/share/desktop-init.sh echo 'passthrough-test-token' 2>/dev/null) && echo \"\$result\" | grep -q 'passthrough-test-token'" + +# Report result +reportResults From dc8cf55a16c1880c364bb27bf39abcc7e478ff82 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 14 Apr 2026 17:32:12 +0530 Subject: [PATCH 42/78] [conda] - Upgrade incompatible pluggy library to prevent conda installation failure. (#1617) --- src/conda/devcontainer-feature.json | 2 +- src/conda/install.sh | 3 +++ test/conda/conda_channel_creation.sh | 14 ++++++++++++++ .../install_conda_package_after_upgrade.sh | 18 ++++++++++++++++++ test/conda/scenarios.json | 12 ++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 test/conda/conda_channel_creation.sh create mode 100644 test/conda/install_conda_package_after_upgrade.sh diff --git a/src/conda/devcontainer-feature.json b/src/conda/devcontainer-feature.json index cd590f9b7..d78e6c92b 100644 --- a/src/conda/devcontainer-feature.json +++ b/src/conda/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "conda", - "version": "1.2.5", + "version": "1.2.6", "name": "Conda", "description": "A cross-platform, language-agnostic binary package manager", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/conda", diff --git a/src/conda/install.sh b/src/conda/install.sh index 73925dd5d..2e312ac58 100644 --- a/src/conda/install.sh +++ b/src/conda/install.sh @@ -179,6 +179,9 @@ if ! conda --version &> /dev/null ; then install_user_package cryptography # Due to https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-40897 install_user_package setuptools + + install_user_package pluggy + fi # Display a notice on conda when not running in GitHub Codespaces diff --git a/test/conda/conda_channel_creation.sh b/test/conda/conda_channel_creation.sh new file mode 100644 index 000000000..d6409a38f --- /dev/null +++ b/test/conda/conda_channel_creation.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +## Test Conda +check "conda-update-conda" bash -c "conda update -c defaults -y conda" +check "conda-install-tensorflow" bash -c "conda create --name test-env -c conda-forge --yes tensorflow" +check "conda-install-pytorch" bash -c "conda create --name test-env -c conda-forge --yes pytorch" + +# Report result +reportResults diff --git a/test/conda/install_conda_package_after_upgrade.sh b/test/conda/install_conda_package_after_upgrade.sh new file mode 100644 index 000000000..cb61c6b65 --- /dev/null +++ b/test/conda/install_conda_package_after_upgrade.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Test that conda can install packages without pluggy incompatibility errors +# This validates the fix for the pluggy/conda version mismatch issue where +# conda self-upgrades but the older pluggy lacks the 'wrapper' attribute +check "conda version" conda --version +check "install pyopenssl" conda install -y -c defaults pyopenssl +check "install cryptography" conda install -y -c defaults cryptography +check "conda-forge" conda config --show channels | grep conda-forge +check "if conda-notice.txt exists" cat /usr/local/etc/vscode-dev-containers/conda-notice.txt + +# Report result +reportResults diff --git a/test/conda/scenarios.json b/test/conda/scenarios.json index ef930a17c..531f32938 100644 --- a/test/conda/scenarios.json +++ b/test/conda/scenarios.json @@ -7,5 +7,17 @@ "addCondaForge": "true" } } + }, + "install_conda_package_after_upgrade": { + "image": "ubuntu:noble", + "features": { + "conda": {} + } + }, + "conda_channel_creation": { + "image": "ubuntu:noble", + "features": { + "conda": {} + } } } From ac2413713e37f570c32d82f43c4eca24be16e5f0 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 14 Apr 2026 19:13:03 +0530 Subject: [PATCH 43/78] Move conda installation from deprecated DEBs to Miniconda installer (#1619) * Move conda installation from deprecated DEBs to Miniconda installer * Fix missing newline at end of conda_channel_creation.sh * Fix missing newline at end of install_conda_package_after_upgrade.sh --------- Co-authored-by: Abdurrahmaan Iqbal --- src/conda/NOTES.md | 2 +- src/conda/README.md | 2 +- src/conda/devcontainer-feature.json | 6 +- src/conda/install.sh | 115 +++++++----------- test/conda/conda_channel_creation.sh | 4 +- test/conda/install_conda.sh | 4 +- .../install_conda_package_after_upgrade.sh | 3 + test/conda/scenarios.json | 2 +- 8 files changed, 58 insertions(+), 80 deletions(-) diff --git a/src/conda/NOTES.md b/src/conda/NOTES.md index ef8657ab4..98189ccc4 100644 --- a/src/conda/NOTES.md +++ b/src/conda/NOTES.md @@ -15,6 +15,6 @@ conda install python=3.7 ## OS Support -This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. +This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. Both `x86_64` and `aarch64` architectures are supported. `bash` is required to execute the `install.sh` script. diff --git a/src/conda/README.md b/src/conda/README.md index 2e123f2c7..eaee45b2d 100644 --- a/src/conda/README.md +++ b/src/conda/README.md @@ -7,7 +7,7 @@ A cross-platform, language-agnostic binary package manager ```json "features": { - "ghcr.io/devcontainers/features/conda:1": {} + "ghcr.io/devcontainers/features/conda:2": {} } ``` diff --git a/src/conda/devcontainer-feature.json b/src/conda/devcontainer-feature.json index d78e6c92b..26d20d6a6 100644 --- a/src/conda/devcontainer-feature.json +++ b/src/conda/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "conda", - "version": "1.2.6", + "version": "2.0.0", "name": "Conda", "description": "A cross-platform, language-agnostic binary package manager", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/conda", @@ -9,8 +9,8 @@ "type": "string", "proposals": [ "latest", - "4.11.0", - "4.12.0" + "24.11.3", + "24.7.1" ], "default": "latest", "description": "Select or enter a conda version." diff --git a/src/conda/install.sh b/src/conda/install.sh index 2e312ac58..4ff0c6fa1 100644 --- a/src/conda/install.sh +++ b/src/conda/install.sh @@ -45,10 +45,14 @@ elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then fi architecture="$(uname -m)" -if [ "${architecture}" != "x86_64" ]; then - echo "(!) Architecture $architecture unsupported" - exit 1 -fi +case "${architecture}" in + x86_64) MINICONDA_ARCH="x86_64" ;; + aarch64|arm64) MINICONDA_ARCH="aarch64" ;; + *) + echo "(!) Architecture $architecture unsupported" + exit 1 + ;; +esac # Checks if packages are installed and installs them if not check_packages() { @@ -75,6 +79,17 @@ install_user_package() { sudo_if "${CONDA_DIR}/bin/python3" -m pip install --user --upgrade "$PACKAGE" } +accept_anaconda_tos_if_needed() { + if ! "${CONDA_DIR}/bin/conda" tos --help > /dev/null 2>&1; then + return 0 + fi + + for channel in "https://repo.anaconda.com/pkgs/main" "https://repo.anaconda.com/pkgs/r"; do + echo "Accepting Conda Terms of Service for ${channel}..." + "${CONDA_DIR}/bin/conda" tos accept --override-channels --channel "${channel}" + done +} + # Install Conda if it's missing if ! conda --version &> /dev/null ; then if ! cat /etc/group | grep -e "^conda:" > /dev/null 2>&1; then @@ -85,78 +100,36 @@ if ! conda --version &> /dev/null ; then # Install dependencies check_packages curl ca-certificates - echo "Installing Conda..." + echo "Installing Conda via Miniconda installer..." - # Download .deb package directly from repository (bypassing SHA1 signature issue) - TEMP_DEB="$(mktemp -t conda_XXXXXX.deb)" - CONDA_REPO_BASE="https://repo.anaconda.com/pkgs/misc/debrepo/conda" - - # Determine package filename based on requested version - ARCH="$(dpkg --print-architecture 2>/dev/null || echo "amd64")" - PACKAGES_URL="https://repo.anaconda.com/pkgs/misc/debrepo/conda/dists/stable/main/binary-${ARCH}/Packages" - - if [ "${VERSION}" = "latest" ]; then - # For latest, we need to query the repository to find the current version - echo "Fetching package list to determine latest version..." - CONDA_PKG_INFO=$(curl -fsSL "${PACKAGES_URL}" | grep -A 30 "^Package: conda$" | head -n 31) - CONDA_VERSION=$(echo "${CONDA_PKG_INFO}" | grep "^Version:" | head -n 1 | awk '{print $2}') - CONDA_FILENAME=$(echo "${CONDA_PKG_INFO}" | grep "^Filename:" | head -n 1 | awk '{print $2}') - - if [ -z "${CONDA_VERSION}" ] || [ -z "${CONDA_FILENAME}" ]; then - echo "ERROR: Could not determine latest conda version or filename from ${PACKAGES_URL}" - echo "This may indicate an unsupported architecture or repository unavailability." - rm -f "${TEMP_DEB}" - exit 1 - fi - - CONDA_PKG_NAME="${CONDA_FILENAME}" - else - # For specific versions, query the Packages file to find the exact filename - echo "Fetching package list to find version ${VERSION}..." - # Search for version pattern - user may specify 4.12.0 but package has 4.12.0-0 - CONDA_PKG_INFO=$(curl -fsSL "${PACKAGES_URL}" | grep -A 30 "^Package: conda$" | grep -B 5 -A 25 "^Version: ${VERSION}") - CONDA_FILENAME=$(echo "${CONDA_PKG_INFO}" | grep "^Filename:" | head -n 1 | awk '{print $2}') - - if [ -z "${CONDA_FILENAME}" ]; then - echo "ERROR: Could not find conda version ${VERSION} in ${PACKAGES_URL}" - echo "Please verify the version specified is valid." - rm -f "${TEMP_DEB}" - exit 1 - fi - - CONDA_PKG_NAME="${CONDA_FILENAME}" - fi - - # Download the .deb package - CONDA_DEB_URL="${CONDA_REPO_BASE}/${CONDA_PKG_NAME}" - echo "Downloading conda package from ${CONDA_DEB_URL}..." - - if ! curl -fsSL "${CONDA_DEB_URL}" -o "${TEMP_DEB}"; then - echo "ERROR: Failed to download conda .deb package from ${CONDA_DEB_URL}" - echo "Please verify the version specified is valid." - rm -f "${TEMP_DEB}" - exit 1 - fi - - # Verify the package was downloaded successfully - if [ ! -f "${TEMP_DEB}" ] || [ ! -s "${TEMP_DEB}" ]; then - echo "ERROR: Conda .deb package file is missing or empty" - rm -f "${TEMP_DEB}" + # Download and run the official Miniconda installer + MINICONDA_INSTALLER="$(mktemp -t miniconda_XXXXXX.sh)" + MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-${MINICONDA_ARCH}.sh" + + echo "Downloading Miniconda installer from ${MINICONDA_URL}..." + if ! curl -fsSL --connect-timeout 10 --max-time 120 "${MINICONDA_URL}" -o "${MINICONDA_INSTALLER}"; then + echo "ERROR: Failed to download Miniconda installer from ${MINICONDA_URL}" + rm -f "${MINICONDA_INSTALLER}" exit 1 fi - - # Install the package using apt (which handles dependencies automatically) - echo "Installing conda package..." - if ! apt-get install -y "${TEMP_DEB}"; then - echo "ERROR: Failed to install conda package" - rm -f "${TEMP_DEB}" - exit 1 + + # Run installer in batch mode (no prompts) and install to CONDA_DIR + bash "${MINICONDA_INSTALLER}" -b -p "${CONDA_DIR}" + rm -f "${MINICONDA_INSTALLER}" + + # Conda defaults channels now require a non-interactive ToS acknowledgement. + accept_anaconda_tos_if_needed + + # Install specific conda version if requested (latest Miniconda already bundles a recent conda) + if [ "${VERSION}" != "latest" ]; then + echo "Installing conda version ${VERSION}..." + if ! "${CONDA_DIR}/bin/conda" install -y "conda=${VERSION}"; then + echo "ERROR: Failed to install conda version ${VERSION}. Please verify the version is valid and available." + exit 1 + fi fi - - # Clean up downloaded package - rm -f "${TEMP_DEB}" - CONDA_SCRIPT="/opt/conda/etc/profile.d/conda.sh" + CONDA_SCRIPT="${CONDA_DIR}/etc/profile.d/conda.sh" . $CONDA_SCRIPT if [ "${ADD_CONDA_FORGE}" = "true" ]; then diff --git a/test/conda/conda_channel_creation.sh b/test/conda/conda_channel_creation.sh index d6409a38f..d1af05d5d 100644 --- a/test/conda/conda_channel_creation.sh +++ b/test/conda/conda_channel_creation.sh @@ -7,8 +7,8 @@ source dev-container-features-test-lib ## Test Conda check "conda-update-conda" bash -c "conda update -c defaults -y conda" -check "conda-install-tensorflow" bash -c "conda create --name test-env -c conda-forge --yes tensorflow" -check "conda-install-pytorch" bash -c "conda create --name test-env -c conda-forge --yes pytorch" +check "conda-install-tensorflow" bash -c "conda create --name tensorflow-test-env -c conda-forge --yes tensorflow" +check "conda-install-pytorch" bash -c "conda create --name pytorch-test-env -c conda-forge --yes pytorch" # Report result reportResults diff --git a/test/conda/install_conda.sh b/test/conda/install_conda.sh index efe7f5e89..c7c67dc51 100644 --- a/test/conda/install_conda.sh +++ b/test/conda/install_conda.sh @@ -5,7 +5,9 @@ set -e # Optional: Import test library source dev-container-features-test-lib -check "conda" conda --version | grep 4.12.0 +check "conda" conda --version +check "conda update" conda update -n base -c defaults -y conda +check "conda updated version" conda --version check "conda-forge" conda config --show channels | grep conda-forge check "if conda-notice.txt exists" cat /usr/local/etc/vscode-dev-containers/conda-notice.txt diff --git a/test/conda/install_conda_package_after_upgrade.sh b/test/conda/install_conda_package_after_upgrade.sh index cb61c6b65..70715a959 100644 --- a/test/conda/install_conda_package_after_upgrade.sh +++ b/test/conda/install_conda_package_after_upgrade.sh @@ -9,8 +9,11 @@ source dev-container-features-test-lib # This validates the fix for the pluggy/conda version mismatch issue where # conda self-upgrades but the older pluggy lacks the 'wrapper' attribute check "conda version" conda --version +check "conda update" conda update -n base -c defaults -y conda +check "conda updated version" conda --version check "install pyopenssl" conda install -y -c defaults pyopenssl check "install cryptography" conda install -y -c defaults cryptography +check "install scipy with bioconda" conda install -y -c bioconda scipy check "conda-forge" conda config --show channels | grep conda-forge check "if conda-notice.txt exists" cat /usr/local/etc/vscode-dev-containers/conda-notice.txt diff --git a/test/conda/scenarios.json b/test/conda/scenarios.json index 531f32938..36eacb10a 100644 --- a/test/conda/scenarios.json +++ b/test/conda/scenarios.json @@ -3,7 +3,7 @@ "image": "ubuntu:noble", "features": { "conda": { - "version": "4.12.0", + "version": "latest", "addCondaForge": "true" } } From 8054e63d4047154fc89141461b683561e496daa4 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Thu, 16 Apr 2026 20:02:34 +0530 Subject: [PATCH 44/78] [conda] - Accept the terms of service for root and target user and conda cleanup (#1622) * [conda] - Accept the terms of service for root and target user and conda cleanup * Removing the version bump. * Removing commented lines. * Retrigger test * Revert the notes change --- src/conda/install.sh | 24 +++++++++++-------- .../conda/conda_channel_creation_with_root.sh | 1 + ...l_conda_package_after_upgrade_with_root.sh | 1 + test/conda/scenarios.json | 16 +++++++++++-- 4 files changed, 30 insertions(+), 12 deletions(-) create mode 120000 test/conda/conda_channel_creation_with_root.sh create mode 120000 test/conda/install_conda_package_after_upgrade_with_root.sh diff --git a/src/conda/install.sh b/src/conda/install.sh index 4ff0c6fa1..fdc050c7a 100644 --- a/src/conda/install.sh +++ b/src/conda/install.sh @@ -86,10 +86,23 @@ accept_anaconda_tos_if_needed() { for channel in "https://repo.anaconda.com/pkgs/main" "https://repo.anaconda.com/pkgs/r"; do echo "Accepting Conda Terms of Service for ${channel}..." + # Accept as root (for install-time commands) "${CONDA_DIR}/bin/conda" tos accept --override-channels --channel "${channel}" + # Accept as the target user (for runtime usage) + sudo_if "${CONDA_DIR}/bin/conda" tos accept --override-channels --channel "${channel}" done } +clean_conda_cache() { + "${CONDA_DIR}/bin/conda" clean --all --yes + find "${CONDA_DIR}" -type f -name '*.pyc' -delete + find "${CONDA_DIR}" -type d -name '__pycache__' -exec rm -rf {} + + rm -rf "${CONDA_DIR}/pkgs/cache" /root/.cache/pip + if [ "${USERNAME}" != "root" ]; then + rm -rf "/home/${USERNAME}/.cache/pip" + fi +} + # Install Conda if it's missing if ! conda --version &> /dev/null ; then if ! cat /etc/group | grep -e "^conda:" > /dev/null 2>&1; then @@ -145,16 +158,7 @@ if ! conda --version &> /dev/null ; then find "${CONDA_DIR}" -type d -print0 | xargs -n 1 -0 chmod g+s - # Temporary fixes - # Due to https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-23491 - install_user_package certifi - # Due to https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-0286 and https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-23931 - install_user_package cryptography - # Due to https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-40897 - install_user_package setuptools - - install_user_package pluggy - + clean_conda_cache fi # Display a notice on conda when not running in GitHub Codespaces diff --git a/test/conda/conda_channel_creation_with_root.sh b/test/conda/conda_channel_creation_with_root.sh new file mode 120000 index 000000000..906864b93 --- /dev/null +++ b/test/conda/conda_channel_creation_with_root.sh @@ -0,0 +1 @@ +conda_channel_creation.sh \ No newline at end of file diff --git a/test/conda/install_conda_package_after_upgrade_with_root.sh b/test/conda/install_conda_package_after_upgrade_with_root.sh new file mode 120000 index 000000000..c3d5608a0 --- /dev/null +++ b/test/conda/install_conda_package_after_upgrade_with_root.sh @@ -0,0 +1 @@ +install_conda_package_after_upgrade.sh \ No newline at end of file diff --git a/test/conda/scenarios.json b/test/conda/scenarios.json index 36eacb10a..f7c2692cb 100644 --- a/test/conda/scenarios.json +++ b/test/conda/scenarios.json @@ -1,6 +1,6 @@ { "install_conda": { - "image": "ubuntu:noble", + "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { "conda": { "version": "latest", @@ -9,12 +9,24 @@ } }, "install_conda_package_after_upgrade": { - "image": "ubuntu:noble", + "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { "conda": {} } }, "conda_channel_creation": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "conda": {} + } + }, + "install_conda_package_after_upgrade_with_root": { + "image": "ubuntu:noble", + "features": { + "conda": {} + } + }, + "conda_channel_creation_with_root": { "image": "ubuntu:noble", "features": { "conda": {} From 87bc90767461e2b964fb9622e592ed83bea4548d Mon Sep 17 00:00:00 2001 From: Kaniska Date: Thu, 16 Apr 2026 23:07:55 +0530 Subject: [PATCH 45/78] [conda] - Version bump (#1623) * [conda] - Version bump * Remove ubuntu:focal from the tests as it's deprecated for over an year * Update baseImage in test-all.yaml Removed comment about ubuntu:focal EOL. * Update test-pr.yaml to remove EOL comment Removed comment about Ubuntu focal EOL in workflow. --------- Co-authored-by: Abdurrahmaan Iqbal --- .github/workflows/test-all.yaml | 1 - .github/workflows/test-manual.yaml | 2 +- .github/workflows/test-pr.yaml | 1 - src/conda/devcontainer-feature.json | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index 4b73845f7..2c1405d9d 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -43,7 +43,6 @@ jobs: ] baseImage: [ - "ubuntu:focal", "ubuntu:jammy", "debian:11", "debian:12", diff --git a/.github/workflows/test-manual.yaml b/.github/workflows/test-manual.yaml index cfee816c2..1373f5215 100644 --- a/.github/workflows/test-manual.yaml +++ b/.github/workflows/test-manual.yaml @@ -9,7 +9,7 @@ on: baseImage: description: "Base image" required: true - default: "ubuntu:focal" + default: "ubuntu:noble" logLevel: description: "Log Level (info/debug/trace)" required: true diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 2747ec582..a10e94d3c 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -50,7 +50,6 @@ jobs: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} baseImage: [ - "ubuntu:focal", "ubuntu:jammy", "debian:11", "debian:12", diff --git a/src/conda/devcontainer-feature.json b/src/conda/devcontainer-feature.json index 26d20d6a6..8c66d4ef1 100644 --- a/src/conda/devcontainer-feature.json +++ b/src/conda/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "conda", - "version": "2.0.0", + "version": "2.0.1", "name": "Conda", "description": "A cross-platform, language-agnostic binary package manager", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/conda", From 732822d359772cb9c7c93af0f357fe9575758dbf Mon Sep 17 00:00:00 2001 From: Caleb Brose <5447118+cmbrose@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:03:43 -0500 Subject: [PATCH 46/78] Add default `copilot update` to `postStartCommand` (#1624) * Add postStartCommand to devcontainer feature * Bump version of copilot-cli to 1.1.0 * feat: conditionally run copilot update based on use-latest flag file Agent-Logs-Url: https://github.com/cmbrose/devcontainer-features/sessions/3627c9ec-26ad-486e-a1ae-a4159d1ec2c5 Co-authored-by: cmbrose <5447118+cmbrose@users.noreply.github.com> * Bump version of copilot-cli to 1.1.1 * Update devcontainer-feature.json * Modify flag file creation for CLI version checks Updated the condition to create a flag file for both 'latest' and 'prerelease' versions. * Update postStartCommand for Copilot CLI * Fix conditional check for CLI version * Bump version of copilot-cli to 1.1.2 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/copilot-cli/devcontainer-feature.json | 3 ++- src/copilot-cli/install.sh | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/copilot-cli/devcontainer-feature.json b/src/copilot-cli/devcontainer-feature.json index ab433e2ae..bfd97286c 100644 --- a/src/copilot-cli/devcontainer-feature.json +++ b/src/copilot-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "copilot-cli", - "version": "1.0.0", + "version": "1.1.2", "name": "GitHub Copilot CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/copilot-cli", "description": "Installs the GitHub Copilot CLI.", @@ -15,6 +15,7 @@ "description": "Select version of the GitHub Copilot CLI, if not latest." } }, + "postStartCommand": "[ -f /etc/devcontainer-copilot-cli/auto-update ] && copilot update || true", "customizations": { "vscode": { "settings": { diff --git a/src/copilot-cli/install.sh b/src/copilot-cli/install.sh index 7250fa642..47c2e3aca 100755 --- a/src/copilot-cli/install.sh +++ b/src/copilot-cli/install.sh @@ -80,3 +80,9 @@ 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 + mkdir -p /etc/devcontainer-copilot-cli + touch /etc/devcontainer-copilot-cli/auto-update +fi + From eea29bc78d2ab97a5465d84e6429fa057f5161dd Mon Sep 17 00:00:00 2001 From: sireeshajonnalagadda Date: Tue, 28 Apr 2026 17:24:07 +0530 Subject: [PATCH 47/78] Add support for configuring npm version in Node devcontainers (#1616) * feat(node): add npm version selection and installation options * add 'lts' and 'latest' options for npm version selection * feat(node): enhance npm installation with compatibility checks and fallback for incompatible Node.js versions * Update src/node/devcontainer-feature.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/node/devcontainer-feature.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat(tests): enhance npm version checks for compatibility and fallback scenarios * Version bump * Version bump * fix(install): update npm version check logic and improve compatibility messaging * fix(install): update npm version check logic and improve compatibility messaging * fix(install): update npm installation loop syntax for clarity --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/node/devcontainer-feature.json | 17 +++- src/node/install.sh | 96 ++++++++++++++++++++ test/node/install_npm_latest.sh | 31 +++++++ test/node/install_npm_latest_incompatible.sh | 30 ++++++ test/node/install_npm_none.sh | 29 ++++++ test/node/install_specific_npm_version.sh | 12 +++ test/node/scenarios.json | 47 +++++++++- 7 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 test/node/install_npm_latest.sh create mode 100644 test/node/install_npm_latest_incompatible.sh create mode 100644 test/node/install_npm_none.sh create mode 100644 test/node/install_specific_npm_version.sh diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index c8ceb966f..2e86262ae 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "1.7.1", + "version": "2.0.0", "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.", @@ -27,6 +27,21 @@ "default": "/usr/local/share/nvm", "description": "The path where NVM will be installed." }, + "npmVersion": { + "type": "string", + "proposals": [ + "lts", + "latest", + "10.9.0", + "10.8.0", + "10.7.0", + "9.9.3", + "8.19.4", + "none" + ], + "default": "none", + "description": "Select or enter a specific NPM version to install globally. Use 'latest' for the latest version, 'none' to skip npm version update, or specify a version like '10.9.0'." + }, "pnpmVersion": { "type": "string", "proposals": [ diff --git a/src/node/install.sh b/src/node/install.sh index 1d89abd0a..20ea85463 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -8,6 +8,7 @@ # Maintainer: The Dev Container spec maintainers export NODE_VERSION="${VERSION:-"lts"}" +export NPM_VERSION="${NPMVERSION:-"lts"}" export PNPM_VERSION="${PNPMVERSION:-"latest"}" export NVM_VERSION="${NVMVERSION:-"latest"}" export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}" @@ -381,6 +382,101 @@ if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then IFS=$OLDIFS fi +# Install or update npm to specific version +if [ -z "${NPM_VERSION}" ] || [ "${NPM_VERSION}" = "none" ]; then + echo "Ignoring NPM version update" +elif bash -c ". '${NVM_DIR}/nvm.sh' && type npm >/dev/null 2>&1"; then + ( + . "${NVM_DIR}/nvm.sh" + [ ! -z "$http_proxy" ] && npm set proxy="$http_proxy" + [ ! -z "$https_proxy" ] && npm set https-proxy="$https_proxy" + [ ! -z "$no_proxy" ] && npm set noproxy="$no_proxy" + echo "Installing npm version ${NPM_VERSION}..." + + CURRENT_NPM_VERSION=$(npm --version 2>/dev/null || echo 'unknown') + echo "Current npm version: $CURRENT_NPM_VERSION" + + # Clear npm cache and extract version numbers + npm cache clean --force 2>/dev/null || true + CURRENT_MAJOR=$(echo "$CURRENT_NPM_VERSION" | cut -d. -f1 || echo "0") + NODE_MAJOR=$(node --version 2>/dev/null | cut -d. -f1 | tr -d 'v' || echo "0") + + # Dynamically check npm's Node.js requirements and auto-fallback if incompatible + ORIGINAL_NPM_VERSION="$NPM_VERSION" + if [ "$NPM_VERSION" != "none" ]; then + echo "Checking npm compatibility requirements..." + NPM_NODE_REQUIREMENT=$(npm view npm@${NPM_VERSION} engines.node 2>/dev/null || echo "") + + if [ -n "$NPM_NODE_REQUIREMENT" ]; then + echo "npm $NPM_VERSION requires Node.js: $NPM_NODE_REQUIREMENT" + + # Extract minimum required Node version from requirement string + MIN_NODE=$(echo "$NPM_NODE_REQUIREMENT" | grep -oE '[0-9]+' | head -1 || echo "0") + + if [ "$MIN_NODE" -gt "0" ] && [ "$NODE_MAJOR" -lt "$MIN_NODE" ]; then + echo "โš ๏ธ WARNING: npm $NPM_VERSION requires Node.js $MIN_NODE+, you have $NODE_MAJOR.x" + + # Find compatible npm version dynamically using same logic + echo "๐Ÿ” Finding compatible npm version for Node.js $NODE_MAJOR.x..." + + # Try npm major versions in descending order to find highest compatible version + for npm_major in 10 9 8 7 6; do + echo "Checking npm $npm_major compatibility..." + FALLBACK_NODE_REQUIREMENT=$(npm view "npm@${npm_major}" engines.node 2>/dev/null || echo "") + + if [ -n "$FALLBACK_NODE_REQUIREMENT" ]; then + MIN_NODE=$(echo "$FALLBACK_NODE_REQUIREMENT" | grep -oE '[0-9]+' | head -1 || echo "0") + + if [ "$MIN_NODE" -le "$NODE_MAJOR" ]; then + # Get latest patch version for this compatible major version + NPM_VERSION=$(npm view "npm@${npm_major}" version 2>/dev/null || echo "") + if [ -n "$NPM_VERSION" ]; then + echo "โœ“ Found compatible npm $NPM_VERSION (requires Node.js $MIN_NODE+)" + echo "๐Ÿ”„ Auto-fallback: Installing compatible npm $NPM_VERSION instead" + break + fi + fi + fi + done + + # If no compatible version found, skip npm installation + if [ "$NPM_VERSION" = "$ORIGINAL_NPM_VERSION" ]; then + echo "โŒ Could not find compatible npm version, keeping current npm" + NPM_VERSION="none" + fi + elif [ "$MIN_NODE" -gt "0" ]; then + echo "โœ“ Node.js $NODE_MAJOR.x meets npm $NPM_VERSION requirement" + fi + else + echo "Could not determine Node.js requirements for npm $NPM_VERSION, proceeding anyway..." + fi + fi + + # Check if npm installation was cancelled due to compatibility issues + if [ "$NPM_VERSION" = "none" ]; then + echo "Skipping npm installation due to compatibility issues." + else + # Try npm installation with retries + for i in 1 2 3; do + echo "Attempt $i: Running npm install -g npm@$NPM_VERSION" + if npm install -g npm@$NPM_VERSION --force --no-audit --no-fund 2>&1; then + NEW_VERSION=$(npm --version 2>/dev/null || echo 'unknown') + echo "Successfully installed npm@${NPM_VERSION}, new version: $NEW_VERSION" + break + else + echo "Attempt $i failed, retrying..." + sleep 2 + if [ $i -eq 3 ]; then + echo "Failed to install npm@${NPM_VERSION} after 3 attempts. Keeping current npm version $(npm --version 2>/dev/null || echo 'unknown')." + fi + fi + done + fi + ) + else + echo "Skip installing/updating npm because npm is not available" + fi + # Install pnpm if [ ! -z "${PNPM_VERSION}" ] && [ "${PNPM_VERSION}" = "none" ]; then echo "Ignoring installation of PNPM" diff --git a/test/node/install_npm_latest.sh b/test/node/install_npm_latest.sh new file mode 100644 index 000000000..e3fa94f67 --- /dev/null +++ b/test/node/install_npm_latest.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# When npmVersion="latest", npm should be upgraded from Node.js bundled version if possible +# Node.js 22 comes with npm 10.x, latest should be 11+ if upgrade succeeds +# If upgrade fails, npm should still work (may remain at bundled version) +check "npm_version_upgraded_or_functional" bash -c " + npm --version >/dev/null + NPM_MAJOR=\$(npm --version | cut -d. -f1) + + if [ \$NPM_MAJOR -ge 11 ]; then + echo 'npm successfully upgraded to version 11+ (\$NPM_MAJOR.x)' + exit 0 + elif [ \$NPM_MAJOR -eq 10 ]; then + echo 'npm upgrade may have failed, but npm 10.x is still functional' + exit 0 + else + echo 'npm version \$NPM_MAJOR.x - unexpected version' + exit 1 + fi +" + +# Also verify pnpm works as configured +check "pnpm_version" bash -c "pnpm -v | grep 8.8.0" + +# Report result +reportResults \ No newline at end of file diff --git a/test/node/install_npm_latest_incompatible.sh b/test/node/install_npm_latest_incompatible.sh new file mode 100644 index 000000000..5db9ad903 --- /dev/null +++ b/test/node/install_npm_latest_incompatible.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Test: npm "latest" with Node.js 16.x (incompatible scenario) +# Should show compatibility warning and auto-fallback to compatible version (npm 9.x) + +# Verify we have Node.js 16.x as expected +check "node_version_16" bash -c "node -v | grep '^v16\.'" + +# Check npm is functional after installation attempt +check "npm_works" bash -c "npm --version" + +# Verify npm version fell back to compatible version for Node 16.x (should be npm 8.x) +check "npm_fallback_version" bash -c " + NPM_MAJOR=\$(npm --version | cut -d. -f1) + if [ \$NPM_MAJOR -eq 8 ]; then + echo 'npm auto-fell back to version 8.x (compatible with Node 16.x)' + exit 0 + else + echo 'npm version \$NPM_MAJOR.x - fallback may not have worked correctly' + exit 1 + fi +" + +# Report result +reportResults \ No newline at end of file diff --git a/test/node/install_npm_none.sh b/test/node/install_npm_none.sh new file mode 100644 index 000000000..5bd07157b --- /dev/null +++ b/test/node/install_npm_none.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# When npmVersion is "none", npm should not be updated from node's bundled version +check "npm_not_updated" bash -c ' + npm --version >/dev/null + + NODE_MAJOR=$(node -p "process.versions.node.split(\".\")[0]") + NPM_MAJOR=$(npm --version | cut -d. -f1) + + case "$NODE_MAJOR" in + 16) EXPECTED_NPM_MAJOR=8 ;; + 18|20|22) EXPECTED_NPM_MAJOR=10 ;; + 24) EXPECTED_NPM_MAJOR=11 ;; + *) + echo "Unsupported Node major for bundled npm assertion: $NODE_MAJOR" + exit 1 + ;; + esac + + [ "$NPM_MAJOR" = "$EXPECTED_NPM_MAJOR" ] +' + +# Report result +reportResults \ No newline at end of file diff --git a/test/node/install_specific_npm_version.sh b/test/node/install_specific_npm_version.sh new file mode 100644 index 000000000..8c541fd07 --- /dev/null +++ b/test/node/install_specific_npm_version.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Verify npm is installed with specific version 10.8.0 +check "npm_specific_version" bash -c "npm -v | grep '^10.8.0'" + +# Report result +reportResults \ No newline at end of file diff --git a/test/node/scenarios.json b/test/node/scenarios.json index e3b4297a1..9459d0e4a 100644 --- a/test/node/scenarios.json +++ b/test/node/scenarios.json @@ -6,7 +6,7 @@ "version": "lts" } } - }, + }, "install_node_debian_bookworm": { "image": "debian:12", "features": { @@ -14,7 +14,7 @@ "version": "lts" } } - }, + }, "nvm_test_fallback": { "image": "debian:11", "features": { @@ -22,7 +22,7 @@ "version": "lts" } } - }, + }, "install_additional_node": { "image": "debian:11", "features": { @@ -98,7 +98,7 @@ "features": { "node": { "version": "22", - "pnpmVersion":"8.8.0" + "pnpmVersion": "8.8.0" } } }, @@ -207,5 +207,42 @@ "version": "lts" } } + }, + "install_specific_npm_version": { + "image": "debian:12", + "features": { + "node": { + "version": "lts", + "npmVersion": "10.8.0" + } + } + }, + "install_npm_none": { + "image": "mcr.microsoft.com/devcontainers/base", + "features": { + "node": { + "version": "24", + "npmVersion": "none" + } + } + }, + "install_npm_latest": { + "image": "debian:12", + "features": { + "node": { + "version": "22", + "npmVersion": "latest", + "pnpmVersion": "8.8.0" + } + } + }, + "install_npm_latest_incompatible": { + "image": "debian:12", + "features": { + "node": { + "version": "16", + "npmVersion": "latest" + } + } } -} +} \ No newline at end of file From 71c999dff6218c6905de7b7a55167fba7eb5709a Mon Sep 17 00:00:00 2001 From: Daeghan Elkin Date: Tue, 28 Apr 2026 07:59:30 -0400 Subject: [PATCH 48/78] Fix `aws-cli` completion for ZSH (#1627) fix(aws-cli): fix ZSH completion installation The ZSH completer script provided by the aws/aws-cli repo is not set up to be used as a completer function, and thus cannot be dropped into a directory like ~/.oh-my-zsh/completions and be expected to work. This change also moves the completer to a more standard location for system-wide ZSH completions, and adds the necessary header to make it work as a completer. --- src/aws-cli/devcontainer-feature.json | 2 +- src/aws-cli/install.sh | 12 +++++------- test/aws-cli/scenarios.json | 9 +++++++++ test/aws-cli/zsh_completion.sh | 17 +++++++++++++++++ 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 test/aws-cli/zsh_completion.sh diff --git a/src/aws-cli/devcontainer-feature.json b/src/aws-cli/devcontainer-feature.json index ed2e20284..75d24ca88 100644 --- a/src/aws-cli/devcontainer-feature.json +++ b/src/aws-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "aws-cli", - "version": "1.1.3", + "version": "1.1.4", "name": "AWS CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/aws-cli", "description": "Installs the AWS CLI along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.", diff --git a/src/aws-cli/install.sh b/src/aws-cli/install.sh index ba6861074..1f6af54bd 100755 --- a/src/aws-cli/install.sh +++ b/src/aws-cli/install.sh @@ -119,16 +119,14 @@ install() { ./aws/install - # kubectl bash completion + # AWS bash completion mkdir -p /etc/bash_completion.d cp ./scripts/vendor/aws_bash_completer /etc/bash_completion.d/aws - # kubectl zsh completion - if [ -e "${USERHOME}/.oh-my-zsh" ]; then - mkdir -p "${USERHOME}/.oh-my-zsh/completions" - cp ./scripts/vendor/aws_zsh_completer.sh "${USERHOME}/.oh-my-zsh/completions/_aws" - chown -R "${USERNAME}" "${USERHOME}/.oh-my-zsh" - fi + # AWS zsh completion + mkdir -p /usr/local/share/zsh/site-functions/ + cp ./scripts/vendor/aws_zsh_completer.sh /usr/local/share/zsh/site-functions/_aws + sed -i '1s/^/#compdef aws\n/' /usr/local/share/zsh/site-functions/_aws rm -rf ./aws } diff --git a/test/aws-cli/scenarios.json b/test/aws-cli/scenarios.json index 9dd703c27..f2577c0ff 100644 --- a/test/aws-cli/scenarios.json +++ b/test/aws-cli/scenarios.json @@ -4,5 +4,14 @@ "features": { "aws-cli": {} } + }, + "zsh_completion": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "common-utils": { + "installZsh": true + }, + "aws-cli": {} + } } } \ No newline at end of file diff --git a/test/aws-cli/zsh_completion.sh b/test/aws-cli/zsh_completion.sh new file mode 100644 index 000000000..ca08e077d --- /dev/null +++ b/test/aws-cli/zsh_completion.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Check that the zsh completion file exists in the correct location +check "zsh completion file installed" test -f /usr/local/share/zsh/site-functions/_aws + +# Check that the completion file has the proper zsh completion header +check "zsh completion file has compdef header" grep -q "^#compdef aws" /usr/local/share/zsh/site-functions/_aws + +# Actual ZSH completion testing is a pain, so just ignoring it for now. + +# Report result +reportResults From 50fd97945b00ccdae3064e26100e768d6081b54e Mon Sep 17 00:00:00 2001 From: Gennadij Ivanov <86804723+ivanov-gv@users.noreply.github.com> Date: Tue, 5 May 2026 18:48:27 +0200 Subject: [PATCH 49/78] #1635 - Fix: changed the golangci-lint script url (#1636) Fix: changed the golangci-lint script url --- src/go/devcontainer-feature.json | 2 +- src/go/install.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go/devcontainer-feature.json b/src/go/devcontainer-feature.json index 8872d6374..b61e93605 100644 --- a/src/go/devcontainer-feature.json +++ b/src/go/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "go", - "version": "1.3.3", + "version": "1.3.4", "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 4286c08a8..db0ac7977 100755 --- a/src/go/install.sh +++ b/src/go/install.sh @@ -325,11 +325,11 @@ if [ "${INSTALL_GO_TOOLS}" = "true" ]; then # Install golangci-lint from precompiled binares if [ "$GOLANGCILINT_VERSION" = "latest" ] || [ "$GOLANGCILINT_VERSION" = "" ]; then echo "Installing golangci-lint latest..." - curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ + curl -fsSL https://golangci-lint.run/install.sh | \ sh -s -- -b "${TARGET_GOPATH}/bin" else echo "Installing golangci-lint ${GOLANGCILINT_VERSION}..." - curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ + curl -fsSL https://golangci-lint.run/install.sh | \ sh -s -- -b "${TARGET_GOPATH}/bin" "v${GOLANGCILINT_VERSION}" fi From 704147d27ebb0de85ec537f303fa87cc9397bbfb Mon Sep 17 00:00:00 2001 From: sireeshajonnalagadda Date: Wed, 6 May 2026 18:52:55 +0530 Subject: [PATCH 50/78] [Node]-Update Readme (#1641) * feat(node): add npm version selection and installation options * add 'lts' and 'latest' options for npm version selection * feat(node): enhance npm installation with compatibility checks and fallback for incompatible Node.js versions * Update src/node/devcontainer-feature.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/node/devcontainer-feature.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat(tests): enhance npm version checks for compatibility and fallback scenarios * Version bump * Version bump * fix(install): update npm version check logic and improve compatibility messaging * fix(install): update npm version check logic and improve compatibility messaging * fix(install): update npm installation loop syntax for clarity * feat(README): update Node.js feature version and add npm version option --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/node/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/node/README.md b/src/node/README.md index 2c6f8cba6..028b47401 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -7,7 +7,16 @@ Installs Node.js, nvm, yarn, pnpm, and needed dependencies. ```json "features": { - "ghcr.io/devcontainers/features/node:1": {} + "ghcr.io/devcontainers/features/node:2": {} +} +``` + +```json +"features": { + "ghcr.io/devcontainers/features/node:2": { + "version": "20", + "npmVersion": "10.8.0" + } } ``` @@ -18,6 +27,7 @@ Installs Node.js, nvm, yarn, pnpm, and needed dependencies. | version | Select or enter a Node.js version to install | string | lts | | nodeGypDependencies | Install dependencies to compile native node modules (node-gyp)? | boolean | true | | nvmInstallPath | The path where NVM will be installed. | string | /usr/local/share/nvm | +| npmVersion | Select or enter a specific NPM version to install globally. Use 'latest' for the latest version, 'none' to skip npm version update, or specify a version like '10.9.0'. | string | none | | pnpmVersion | Select or enter the PNPM version to install | string | latest | | nvmVersion | Version of NVM to install. | string | latest | | installYarnUsingApt | On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs. | boolean | false | From 58f4a1f28b18a2d46883000725f55c2fa698e425 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 14:40:17 +0530 Subject: [PATCH 51/78] =?UTF-8?q?fix:=20docker-in-docker=20on=20Ubuntu=202?= =?UTF-8?q?6.04=20=E2=80=94=20verify=20iptables-legacy=20works=20before=20?= =?UTF-8?q?switching=20(#1637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * fix: support Ubuntu 26.04 (plucky) by checking iptables-legacy works before switching, falling back to iptables-nft Agent-Logs-Url: https://github.com/devcontainers/features/sessions/af4f00a5-8bfc-472b-97e3-735ddf7a07c1 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * fix: also verify iptables-nft works before switching to it Agent-Logs-Url: https://github.com/devcontainers/features/sessions/af4f00a5-8bfc-472b-97e3-735ddf7a07c1 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * fix: use correct Ubuntu 26.04 codename 'resolute' instead of 'plucky' Agent-Logs-Url: https://github.com/devcontainers/features/sessions/60277e16-b948-4f67-8140-1d3f1cda6941 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * chore: bump docker-in-docker feature version to 2.17.0 Agent-Logs-Url: https://github.com/devcontainers/features/sessions/f340a34b-66fa-45bd-85f2-94b32c943241 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * test: set moby: false for resolute test scenario Agent-Logs-Url: https://github.com/devcontainers/features/sessions/ce62c78c-ff28-4766-bd7f-2b5a57a349ec Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * docs: note Ubuntu Resolute exceptional behavior in NOTES.md Agent-Logs-Url: https://github.com/devcontainers/features/sessions/8983ff12-365e-49de-bd65-47614caf8539 Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> * Correction on the iptables switching logic --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska244 <186041440+Kaniska244@users.noreply.github.com> Co-authored-by: Kaniska --- src/docker-in-docker/NOTES.md | 2 ++ .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 31 ++++++++++++++----- .../docker_build_ubuntu_resolute.sh | 14 +++++++++ test/docker-in-docker/scenarios.json | 8 +++++ 5 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 test/docker-in-docker/docker_build_ubuntu_resolute.sh diff --git a/src/docker-in-docker/NOTES.md b/src/docker-in-docker/NOTES.md index c7fb26137..797bbcc81 100644 --- a/src/docker-in-docker/NOTES.md +++ b/src/docker-in-docker/NOTES.md @@ -15,4 +15,6 @@ This Feature should work on recent versions of Debian/Ubuntu-based distributions Debian Trixie (13) does not include moby-cli and related system packages, so the feature cannot install with "moby": "true". To use this feature on Trixie, please set "moby": "false" or choose a different base image (for example, Ubuntu 24.04). +Ubuntu 26.04 (Resolute) does not currently have moby packages available, so the feature cannot install with "moby": "true". To use this feature on Resolute, please set "moby": "false". Additionally, the kernel on Ubuntu 26.04 no longer supports legacy iptables NAT tables, so the feature automatically falls back to `iptables-nft` when `iptables-legacy` is not functional. + `bash` is required to execute the `install.sh` script. diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 4c792e8f4..bdb4cb291 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": "2.16.1", + "version": "2.17.0", "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 5af320b0b..dd9d09fa3 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -20,7 +20,7 @@ INSTALL_DOCKER_COMPOSE_SWITCH="${INSTALLDOCKERCOMPOSESWITCH:-"false"}" MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" MICROSOFT_GPG_KEYS_ROLLING_URI="https://packages.microsoft.com/keys/microsoft-rolling.asc" DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal jammy noble" -DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal hirsute impish jammy noble" +DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal hirsute impish jammy noble resolute" DISABLE_IP6_TABLES="${DISABLEIP6TABLES:-false}" # Default: Exit on any failure. @@ -249,10 +249,10 @@ if [ "${ID}" = "azurelinux" ]; then VERSION_CODENAME="azurelinux${VERSION_ID}" fi -# Prevent attempting to install Moby on Debian trixie (packages removed) -if [ "${USE_MOBY}" = "true" ] && [ "${ID}" = "debian" ] && [ "${VERSION_CODENAME}" = "trixie" ]; then - err "The 'moby' option is not supported on Debian 'trixie' because 'moby-cli' and related system packages have been removed from that distribution." - err "To continue, either set the feature option '\"moby\": false' or use a different base image (for example: 'debian:bookworm' or 'ubuntu-24.04')." +# Prevent attempting to install Moby on Debian trixie/resolute (packages removed) +if [ "${USE_MOBY}" = "true" ] && [ "${ADJUSTED_ID}" = "debian" ] && ([ "${VERSION_CODENAME}" = "trixie" ] || [ "${VERSION_CODENAME}" = "resolute" ]); then + err "The 'moby' option is not supported on ${ID} '${VERSION_CODENAME}' because 'moby-cli' and related system packages are not available in that distribution." + err "To continue, either set the feature option '\"moby\": false' or use a different base image." exit 1 fi @@ -311,9 +311,24 @@ if [ "${ADJUSTED_ID}" = "debian" ] && command -v update-ca-certificates > /dev/n fi # Swap to legacy iptables for compatibility (Debian only) -if [ "${ADJUSTED_ID}" = "debian" ] && type iptables-legacy > /dev/null 2>&1; then - update-alternatives --set iptables /usr/sbin/iptables-legacy - update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy +if [ "${ADJUSTED_ID}" = "debian" ]; then + # On distros where legacy iptables is no longer kernel-supported (e.g. Ubuntu 26.04 / resolute), + # prefer iptables-nft. Otherwise prefer legacy for backward compatibility. + use_nft=false + case "${VERSION_CODENAME}" in + resolute) use_nft=true ;; + esac + + if [ "${use_nft}" = "true" ] && type iptables-nft > /dev/null 2>&1; then + update-alternatives --set iptables /usr/sbin/iptables-nft || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true + elif type iptables-legacy > /dev/null 2>&1; then + update-alternatives --set iptables /usr/sbin/iptables-legacy || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true + elif type iptables-nft > /dev/null 2>&1; then + update-alternatives --set iptables /usr/sbin/iptables-nft || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true + fi fi # Set up the necessary repositories diff --git a/test/docker-in-docker/docker_build_ubuntu_resolute.sh b/test/docker-in-docker/docker_build_ubuntu_resolute.sh new file mode 100644 index 000000000..c9fd7affc --- /dev/null +++ b/test/docker-in-docker/docker_build_ubuntu_resolute.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "docker-buildx" docker buildx version +check "docker-build" docker build ./ +check "docker-buildx-path" bash -c "ls -la /usr/libexec/docker/cli-plugins/docker-buildx" + +# Report result +reportResults diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index baeaa6769..ad8c2ce48 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -154,6 +154,14 @@ } } }, + "docker_build_ubuntu_resolute": { + "image": "ubuntu:resolute", + "features": { + "docker-in-docker": { + "moby": false + } + } + }, "docker_specific_moby_buildx": { "image": "ubuntu:noble", "features": { From 8c471574f1b39e260e88ddcb858ab1681dafbdac Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 15:02:45 +0100 Subject: [PATCH 52/78] Install only missing packages across all common-utils package sets (#1644) * Initial plan * Fix bubblewrap/socat installation in common-utils (install regardless of PACKAGES_ALREADY_INSTALLED) * Install bubblewrap/socat only when missing in common-utils * Refine missing-package checks in common-utils * Check all common-utils packages and install only missing ones * Address PR review: fix dpkg-query format, remove dead variable, add docs and tests - Add \n to dpkg-query format string so glob patterns like libicu[0-9][0-9] that match multiple packages get each status on its own line - Remove dead PACKAGES_ALREADY_INSTALLED variable from all three install functions and the marker file (per-package checks make it redundant) - Document bubblewrap and socat in NOTES.md - Add bubblewrap/socat checks to alpine, alma-9, fedora, and rocky-9 tests# * Add bubblewrap/socat checks to Ubuntu test scenarios (jammy, noble) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Abdurrahmaan Iqbal --- src/common-utils/NOTES.md | 7 + src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 410 +++++++++++---------- test/common-utils/alma-9.sh | 2 + test/common-utils/alpine.sh | 2 + test/common-utils/fedora.sh | 2 + test/common-utils/jammy.sh | 2 + test/common-utils/noble.sh | 2 + test/common-utils/rocky-9.sh | 2 + test/common-utils/test.sh | 2 + 10 files changed, 240 insertions(+), 193 deletions(-) diff --git a/src/common-utils/NOTES.md b/src/common-utils/NOTES.md index f2c7aa72e..9faa2eedd 100644 --- a/src/common-utils/NOTES.md +++ b/src/common-utils/NOTES.md @@ -2,6 +2,13 @@ This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, RockyLinux, and Alpine Linux. +## Included utilities + +In addition to the common CLI tools (curl, wget, git, jq, nano, vim, etc.), this Feature installs: + +- **bubblewrap** (`bwrap`) โ€” a lightweight sandboxing tool used as a dependency by some desktop and container tooling. +- **socat** โ€” a multipurpose relay for bidirectional data transfer between two independent data channels (e.g., sockets, files, pipes). + ## Using with dev container images This Feature is used in many of the [dev container images](https://github.com/search?q=repo%3Adevcontainers%2Fimages+%22ghcr.io%2Fdevcontainers%2Ffeatures%2Fcommon-utils%22&type=code), as a result diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 4ebbd3074..92d47fc3c 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.5.7", + "version": "2.5.8", "name": "Common Utilities", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/common-utils", "description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.", diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index 3f6b13477..eab75ced3 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -30,80 +30,80 @@ install_debian_packages() { export DEBIAN_FRONTEND=noninteractive local package_list="" - if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then - package_list="${package_list} \ - apt-utils \ - bash-completion \ - openssh-client \ - gnupg2 \ - dirmngr \ - iproute2 \ - procps \ - lsof \ - htop \ - net-tools \ - psmisc \ - curl \ - tree \ - wget \ - rsync \ - ca-certificates \ - unzip \ - bzip2 \ - xz-utils \ - zip \ - nano \ - vim-tiny \ - less \ - jq \ - lsb-release \ - apt-transport-https \ - dialog \ - libc6 \ - libgcc1 \ - libkrb5-3 \ - libgssapi-krb5-2 \ - libicu[0-9][0-9] \ - liblttng-ust[0-9] \ - libstdc++6 \ - zlib1g \ - locales \ - sudo \ - ncdu \ - man-db \ - strace \ - manpages \ - manpages-dev \ - init-system-helpers" - - if [ "${INSTALL_SSL}" = "true" ]; then - # Include libssl1.1 if available - if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then - package_list="${package_list} libssl1.1" - fi + package_list="${package_list} \ + apt-utils \ + bash-completion \ + openssh-client \ + gnupg2 \ + dirmngr \ + iproute2 \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + tree \ + wget \ + rsync \ + ca-certificates \ + unzip \ + bzip2 \ + xz-utils \ + zip \ + nano \ + vim-tiny \ + less \ + jq \ + lsb-release \ + apt-transport-https \ + dialog \ + libc6 \ + libgcc1 \ + libkrb5-3 \ + libgssapi-krb5-2 \ + libicu[0-9][0-9] \ + liblttng-ust[0-9] \ + libstdc++6 \ + zlib1g \ + locales \ + sudo \ + ncdu \ + man-db \ + strace \ + manpages \ + manpages-dev \ + init-system-helpers \ + bubblewrap \ + socat" + + if [ "${INSTALL_SSL}" = "true" ]; then + # Include libssl1.1 if available + if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then + package_list="${package_list} libssl1.1" + fi - # Include libssl3 if available - if [[ ! -z $(apt-cache --names-only search ^libssl3$) ]]; then - package_list="${package_list} libssl3" - fi + # Include libssl3 if available + if [[ ! -z $(apt-cache --names-only search ^libssl3$) ]]; then + package_list="${package_list} libssl3" + fi - # Include appropriate version of libssl1.0.x if available - local libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') - if [ "$(echo "$libssl_package" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then - if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then - # Debian 9 - package_list="${package_list} libssl1.0.2" - elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then - # Ubuntu 18.04 - package_list="${package_list} libssl1.0.0" - fi + # Include appropriate version of libssl1.0.x if available + local libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') + if [ "$(echo "$libssl_package" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then + if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then + # Debian 9 + package_list="${package_list} libssl1.0.2" + elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then + # Ubuntu 18.04 + package_list="${package_list} libssl1.0.0" fi fi + fi - # Include git if not already installed (may be more recent than distro version) - if ! type git > /dev/null 2>&1; then - package_list="${package_list} git" - fi + # Include git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + package_list="${package_list} git" fi # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian @@ -128,11 +128,22 @@ install_debian_packages() { package_list="${package_list} manpages-posix manpages-posix-dev" fi - # Install the list of packages - echo "Packages to verify are installed: ${package_list}" - rm -rf /var/lib/apt/lists/* - apt-get update -y - apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) + local missing_package_list="" + local packages=() + read -r -a packages <<< "${package_list}" + for package in "${packages[@]}"; do + if ! dpkg-query -W -f='${db:Status-Abbrev}\n' "${package}" 2>/dev/null | grep -q '^ii'; then + missing_package_list="${missing_package_list} ${package}" + fi + done + + # Install the list of missing packages + if [ -n "${missing_package_list}" ]; then + echo "Packages to verify are installed: ${missing_package_list}" + rm -rf /var/lib/apt/lists/* + apt-get update -y + apt-get -y install --no-install-recommends ${missing_package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) + fi # Install zsh (and recommended packages) if needed if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then @@ -152,8 +163,6 @@ install_debian_packages() { LOCALE_ALREADY_SET="true" fi - PACKAGES_ALREADY_INSTALLED="true" - # Clean up apt-get -y clean rm -rf /var/lib/apt/lists/* @@ -177,64 +186,64 @@ install_redhat_packages() { exit 1 fi - if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then - package_list="${package_list} \ - gawk \ - bash-completion \ - openssh-clients \ - gnupg2 \ - iproute \ - procps \ - lsof \ - net-tools \ - psmisc \ - wget \ - ca-certificates \ - rsync \ - unzip \ - xz \ - zip \ - nano \ - vim-minimal \ - less \ - jq \ - openssl-libs \ - krb5-libs \ - libicu \ - zlib \ - sudo \ - sed \ - grep \ - which \ - man-db \ - strace" - - # rockylinux:9 installs 'curl-minimal' which clashes with 'curl' - # Install 'curl' for every OS except this rockylinux:9 - if [[ "${ID}" = "rocky" ]] && [[ "${VERSION}" != *"9."* ]]; then - package_list="${package_list} curl" - fi + package_list="${package_list} \ + gawk \ + bash-completion \ + openssh-clients \ + gnupg2 \ + iproute \ + procps \ + lsof \ + net-tools \ + psmisc \ + wget \ + ca-certificates \ + rsync \ + unzip \ + xz \ + zip \ + nano \ + vim-minimal \ + less \ + jq \ + openssl-libs \ + krb5-libs \ + libicu \ + zlib \ + sudo \ + sed \ + grep \ + which \ + man-db \ + strace \ + bubblewrap \ + socat" - # Install OpenSSL 1.0 compat if needed - if ${install_cmd} -q list compat-openssl10 >/dev/null 2>&1; then - package_list="${package_list} compat-openssl10" - fi + # rockylinux:9 installs 'curl-minimal' which clashes with 'curl' + # Install 'curl' for every OS except this rockylinux:9 + if [[ "${ID}" = "rocky" ]] && [[ "${VERSION}" != *"9."* ]]; then + package_list="${package_list} curl" + fi - # Install lsb_release if available - if ${install_cmd} -q list redhat-lsb-core >/dev/null 2>&1; then - package_list="${package_list} redhat-lsb-core" - fi + # Install OpenSSL 1.0 compat if needed + if ${install_cmd} -q list compat-openssl10 >/dev/null 2>&1; then + package_list="${package_list} compat-openssl10" + fi - # Install git if not already installed (may be more recent than distro version) - if ! type git > /dev/null 2>&1; then - package_list="${package_list} git" - fi + # Install lsb_release if available + if ${install_cmd} -q list redhat-lsb-core >/dev/null 2>&1; then + package_list="${package_list} redhat-lsb-core" + fi - # Install EPEL repository if needed (required to install 'jq' for CentOS) - if [[ "${ID}" = "centos" ]] && ! rpm -q jq >/dev/null 2>&1; then - ${install_cmd} -y install epel-release - remove_epel="true" - fi + # Install git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + package_list="${package_list} git" + fi + + # Install EPEL repository if needed (required to install 'jq' for CentOS) + if [[ "${ID}" = "centos" ]] && ! rpm -q jq >/dev/null 2>&1; then + ${install_cmd} -y install epel-release + remove_epel="true" fi # Install zsh if needed @@ -242,13 +251,22 @@ install_redhat_packages() { package_list="${package_list} zsh" fi - if [ -n "${package_list}" ]; then - echo "Packages to verify are installed: ${package_list}" + local missing_package_list="" + local packages=() + read -r -a packages <<< "${package_list}" + for package in "${packages[@]}"; do + if ! rpm -q "${package}" >/dev/null 2>&1; then + missing_package_list="${missing_package_list} ${package}" + fi + done + + if [ -n "${missing_package_list}" ]; then + echo "Packages to verify are installed: ${missing_package_list}" echo "Running ${install_cmd} install..." if [ "${install_cmd}" = "dnf" ]; then - ${install_cmd} -y install --allowerasing ${package_list} + ${install_cmd} -y install --allowerasing ${missing_package_list} else - ${install_cmd} -y install ${package_list} + ${install_cmd} -y install ${missing_package_list} fi fi @@ -261,77 +279,86 @@ install_redhat_packages() { if [[ "${remove_epel}" = "true" ]]; then ${install_cmd} -y remove epel-release fi - - PACKAGES_ALREADY_INSTALLED="true" } # Alpine Linux packages install_alpine_packages() { apk update + local package_list="" - if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then - apk add --no-cache \ - openssh-client \ - bash-completion \ - gnupg \ - procps \ - lsof \ - htop \ - net-tools \ - psmisc \ - curl \ - wget \ - rsync \ - ca-certificates \ - unzip \ - xz \ - zip \ - nano \ - vim \ - less \ - jq \ - libgcc \ - libstdc++ \ - krb5-libs \ - libintl \ - lttng-ust \ - tzdata \ - userspace-rcu \ - zlib \ - sudo \ - coreutils \ - sed \ - grep \ - which \ - ncdu \ - shadow \ - strace - - # # Include libssl1.1 if available (not available for 3.19 and newer) - LIBSSL1_PKG=libssl1.1 - if [[ $(apk search --no-cache -a $LIBSSL1_PKG | grep $LIBSSL1_PKG) ]]; then - apk add --no-cache $LIBSSL1_PKG - fi + package_list="${package_list} \ + openssh-client \ + bash-completion \ + gnupg \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + wget \ + rsync \ + ca-certificates \ + unzip \ + xz \ + zip \ + nano \ + vim \ + less \ + jq \ + libgcc \ + libstdc++ \ + krb5-libs \ + libintl \ + lttng-ust \ + tzdata \ + userspace-rcu \ + zlib \ + sudo \ + coreutils \ + sed \ + grep \ + which \ + ncdu \ + shadow \ + strace \ + bubblewrap \ + socat" - # Install man pages - package name varies between 3.12 and earlier versions - if apk info man > /dev/null 2>&1; then - apk add --no-cache man man-pages - else - apk add --no-cache mandoc man-pages - fi + # # Include libssl1.1 if available (not available for 3.19 and newer) + LIBSSL1_PKG=libssl1.1 + if [[ $(apk search --no-cache -a $LIBSSL1_PKG | grep $LIBSSL1_PKG) ]]; then + package_list="${package_list} $LIBSSL1_PKG" + fi - # Install git if not already installed (may be more recent than distro version) - if ! type git > /dev/null 2>&1; then - apk add --no-cache git - fi + # Install man pages - package name varies between 3.12 and earlier versions + if apk info man > /dev/null 2>&1; then + package_list="${package_list} man man-pages" + else + package_list="${package_list} mandoc man-pages" + fi + + # Install git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + package_list="${package_list} git" fi # Install zsh if needed if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then - apk add --no-cache zsh + package_list="${package_list} zsh" fi - PACKAGES_ALREADY_INSTALLED="true" + local missing_package_list="" + local packages=() + read -r -a packages <<< "${package_list}" + for package in "${packages[@]}"; do + if ! apk info -e "${package}" >/dev/null 2>&1; then + missing_package_list="${missing_package_list} ${package}" + fi + done + if [ -n "${missing_package_list}" ]; then + apk add --no-cache ${missing_package_list} + fi } # ****************** @@ -609,7 +636,6 @@ if [ ! -d "/usr/local/etc/vscode-dev-containers" ]; then mkdir -p "$(dirname "${MARKER_FILE}")" fi echo -e "\ - PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ diff --git a/test/common-utils/alma-9.sh b/test/common-utils/alma-9.sh index cb2b339e1..c0771df14 100755 --- a/test/common-utils/alma-9.sh +++ b/test/common-utils/alma-9.sh @@ -11,6 +11,8 @@ check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${PLATFORM_ID}" = "platform:el9" check "curl" curl --version check "jq" jq --version +check "bubblewrap" bwrap --version +check "socat" socat -V # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/alpine.sh b/test/common-utils/alpine.sh index c5ff86613..f60732527 100755 --- a/test/common-utils/alpine.sh +++ b/test/common-utils/alpine.sh @@ -9,6 +9,8 @@ source dev-container-features-test-lib . /etc/os-release check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${ID}" = "alpine" +check "bubblewrap" bwrap --version +check "socat" socat -V # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/fedora.sh b/test/common-utils/fedora.sh index 67706d49a..e52f99bff 100755 --- a/test/common-utils/fedora.sh +++ b/test/common-utils/fedora.sh @@ -10,6 +10,8 @@ source dev-container-features-test-lib check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${ID}" = "fedora" check "jq" jq --version +check "bubblewrap" bwrap --version +check "socat" socat -V # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/jammy.sh b/test/common-utils/jammy.sh index f11cac69e..e09765168 100755 --- a/test/common-utils/jammy.sh +++ b/test/common-utils/jammy.sh @@ -9,6 +9,8 @@ source dev-container-features-test-lib . /etc/os-release check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${VERSION_CODENAME}" = "jammy" +check "bubblewrap" bwrap --version +check "socat" socat -V # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/noble.sh b/test/common-utils/noble.sh index 78ad146e3..e1d578271 100644 --- a/test/common-utils/noble.sh +++ b/test/common-utils/noble.sh @@ -9,6 +9,8 @@ source dev-container-features-test-lib . /etc/os-release check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${VERSION_CODENAME}" = "noble" +check "bubblewrap" bwrap --version +check "socat" socat -V # Report result reportResults diff --git a/test/common-utils/rocky-9.sh b/test/common-utils/rocky-9.sh index cb2b339e1..c0771df14 100755 --- a/test/common-utils/rocky-9.sh +++ b/test/common-utils/rocky-9.sh @@ -11,6 +11,8 @@ check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${PLATFORM_ID}" = "platform:el9" check "curl" curl --version check "jq" jq --version +check "bubblewrap" bwrap --version +check "socat" socat -V # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/test.sh b/test/common-utils/test.sh index 5e16a33c7..46cb119be 100755 --- a/test/common-utils/test.sh +++ b/test/common-utils/test.sh @@ -10,6 +10,8 @@ check "jq" jq --version check "curl" curl --version check "git" git --version check "zsh" zsh --version +check "bubblewrap" bwrap --version +check "socat" socat -V check "ps" ps --version check "Oh My Zsh! theme" test -e $HOME/.oh-my-zsh/custom/themes/devcontainers.zsh-theme check "zsh theme symlink" test -e $HOME/.oh-my-zsh/custom/themes/codespaces.zsh-theme From a646de42368ffd0df9110a0b07d5bcde4ad2f8c4 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Mon, 18 May 2026 17:58:24 +0530 Subject: [PATCH 53/78] fix(docker-in-docker): disable containerd erofs snapshotter to fix dockerd startup (#1645) * Test case * Modify the script * Trigger the test * Change installation script to install erofs-utils upfront * Install docker-compose v1 with python venv * Force load erofs to replicate the same setup as reported in the issue * Disable erofs filesystem * Start own containerd process * Major version bump * Add docker probe in some cases. * Support arm64 for azurelinux --- .github/workflows/test-pr-arm64.yaml | 9 ++ src/docker-in-docker/NOTES.md | 2 +- src/docker-in-docker/README.md | 2 +- .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 133 ++++++++++++++++-- .../dockerIp6tablesDisabledTest.sh | 2 +- test/docker-in-docker/docker_build_older.sh | 2 +- .../pin_docker-ce_version_moby_false.sh | 1 + 8 files changed, 136 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index 113ce39b5..f05048e1b 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -8,6 +8,8 @@ on: paths: - "src/powershell/**" - "test/powershell/**" + - "src/docker-in-docker/**" + - "test/docker-in-docker/**" jobs: detect-changes: @@ -23,6 +25,7 @@ jobs: # : ./**//** filters: | powershell: ./**/powershell/** + docker-in-docker: ./**/docker-in-docker/** test: needs: [detect-changes] @@ -42,9 +45,15 @@ jobs: "mcr.microsoft.com/devcontainers/base:debian", "mcr.microsoft.com/devcontainers/base:noble" ] + exclude: + - features: docker-in-docker + baseImage: mcr.microsoft.com/devcontainers/base:debian steps: - uses: actions/checkout@v6 + - name: "Load erofs module and verify" + run: sudo modprobe erofs && grep erofs /proc/filesystems + - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/src/docker-in-docker/NOTES.md b/src/docker-in-docker/NOTES.md index 797bbcc81..67f736f59 100644 --- a/src/docker-in-docker/NOTES.md +++ b/src/docker-in-docker/NOTES.md @@ -4,7 +4,7 @@ This docker-in-docker Dev Container Feature is roughly based on the [official do * As the name implies, the Feature is expected to work when the host is running Docker (or the OSS Moby container engine it is built on). It may be possible to get running in other container engines, but it has not been tested with them. * The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, like in this example: ``` - FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:16 + FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:24 ``` See [Issue #219](https://github.com/devcontainers/features/issues/219) for more details. diff --git a/src/docker-in-docker/README.md b/src/docker-in-docker/README.md index 9c5370c4e..c58283317 100644 --- a/src/docker-in-docker/README.md +++ b/src/docker-in-docker/README.md @@ -7,7 +7,7 @@ Create child containers *inside* a container, independent from the host's docker ```json "features": { - "ghcr.io/devcontainers/features/docker-in-docker:2": {} + "ghcr.io/devcontainers/features/docker-in-docker:3": {} } ``` diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index bdb4cb291..406627f7e 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": "2.17.0", + "version": "3.0.0", "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 dd9d09fa3..e9740efc5 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -287,10 +287,13 @@ else fi # Install base dependencies +# Note: erofs-utils provides mkfs.erofs, required by containerd >= 2.3.x snapshotter +# to avoid "failed to check mkfs.erofs availability" errors at dockerd startup +# (see https://github.com/devcontainers/features/issues/1642). base_packages="curl ca-certificates pigz iptables gnupg2 wget jq" case ${ADJUSTED_ID} in debian) - check_packages apt-transport-https $base_packages dirmngr + check_packages apt-transport-https $base_packages dirmngr erofs-utils ;; rhel) check_packages $base_packages tar gawk shadow-utils policycoreutils procps-ng systemd-libs systemd-devel @@ -621,9 +624,19 @@ else # Download packages manually using curl since tdnf doesn't support download echo "(*) Downloading Docker CE packages manually..." - + + # Derive repo arch from the current platform. The Docker CE centos + # repo uses 'x86_64' and 'aarch64' as the per-arch directory names, + # which matches the values produced by `rpm --eval '%{_arch}'` / + # `uname -m` for those platforms. + case "${architecture}" in + amd64|x86_64) repo_arch="x86_64" ;; + arm64|aarch64) repo_arch="aarch64" ;; + *) repo_arch="${architecture}" ;; + esac + # Get the repository baseurl - repo_baseurl="https://download.docker.com/linux/centos/9/x86_64/stable" + repo_baseurl="https://download.docker.com/linux/centos/9/${repo_arch}/stable" # Download packages directly cd /tmp/docker-ce-install @@ -640,12 +653,12 @@ else echo "(*) Attempting to download Docker CE packages from repository..." # Try to download latest packages if specific version fails - if ! curl -fsSL "${repo_baseurl}/Packages/docker-ce-${docker_ce_version}.el9.x86_64.rpm" -o docker-ce.rpm 2>/dev/null; then + if ! curl -fsSL "${repo_baseurl}/Packages/docker-ce-${docker_ce_version}.el9.${repo_arch}.rpm" -o docker-ce.rpm 2>/dev/null; then # Fallback: try to get latest available version echo "(*) Specific version not found, trying latest..." - latest_docker=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'docker-ce-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1) - latest_cli=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'docker-ce-cli-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1) - latest_containerd=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'containerd\.io-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1) + latest_docker=$(curl -s "${repo_baseurl}/Packages/" | grep -o "docker-ce-[0-9][^\"]*\.el9\.${repo_arch}\.rpm" | head -1) + latest_cli=$(curl -s "${repo_baseurl}/Packages/" | grep -o "docker-ce-cli-[0-9][^\"]*\.el9\.${repo_arch}\.rpm" | head -1) + latest_containerd=$(curl -s "${repo_baseurl}/Packages/" | grep -o "containerd\.io-[0-9][^\"]*\.el9\.${repo_arch}\.rpm" | head -1) if [ -n "${latest_docker}" ]; then curl -fsSL "${repo_baseurl}/Packages/${latest_docker}" -o docker-ce.rpm @@ -731,11 +744,21 @@ if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then err "Docker compose v1 is unavailable for 'bookworm' on Arm64. Kindly switch to use v2" exit 1 else - # Use pip to get a version that runs on this architecture + # Use pip (inside an isolated venv) to get a version that runs on this architecture. + # A dedicated venv avoids PEP 668 "externally-managed-environment" errors on newer + # distros (Debian trixie, Ubuntu noble, etc.) and guarantees we do not modify or + # shadow the distro-managed system Python site-packages. check_packages python3-minimal python3-pip libffi-dev python3-venv - echo "(*) Installing docker compose v1 via pip..." - export PYTHONUSERBASE=/usr/local - pip3 install --disable-pip-version-check --no-cache-dir --user "Cython<3.0" pyyaml wheel docker-compose --no-build-isolation + echo "(*) Installing docker compose v1 via pip into an isolated virtualenv..." + + compose_v1_venv="/usr/local/share/docker-compose-v1-venv" + python3 -m venv "${compose_v1_venv}" + "${compose_v1_venv}/bin/pip" install --disable-pip-version-check --no-cache-dir --upgrade pip setuptools wheel + "${compose_v1_venv}/bin/pip" install --disable-pip-version-check --no-cache-dir "Cython<3.0" pyyaml docker-compose --no-build-isolation + + # Expose the venv's docker-compose entrypoint on PATH at the expected location. + ln -sf "${compose_v1_venv}/bin/docker-compose" "${docker_compose_path}" + chmod +x "${docker_compose_path}" fi else compose_version=${DOCKER_DASH_COMPOSE_VERSION#v} @@ -876,6 +899,58 @@ if [ "$DISABLE_IP6_TABLES" == true ]; then fi fi +# Workaround for https://github.com/devcontainers/features/issues/1642 +# containerd >= 2.3 ships an erofs snapshotter that requires mkfs.erofs >= 1.7. +# Older distros (Debian 12, Ubuntu 22.04) ship erofs-utils 1.4/1.5, so when the +# host kernel exposes the 'erofs' filesystem the snapshotter fails to +# initialize and dockerd times out waiting for containerd. Disable the plugin +# via the top-level `disabled_plugins` list so containerd always uses +# overlayfs, regardless of distro / mkfs.erofs version. +mkdir -p /etc/containerd +if [ ! -s /etc/containerd/config.toml ]; then + if command -v containerd >/dev/null 2>&1; then + containerd config default > /etc/containerd/config.toml 2>/dev/null || : > /etc/containerd/config.toml + elif [ -x /usr/sbin/containerd ]; then + /usr/sbin/containerd config default > /etc/containerd/config.toml 2>/dev/null || : > /etc/containerd/config.toml + elif [ -x /usr/bin/containerd ]; then + /usr/bin/containerd config default > /etc/containerd/config.toml 2>/dev/null || : > /etc/containerd/config.toml + else + : > /etc/containerd/config.toml + fi +fi + +EROFS_PLUGIN_URI='io.containerd.snapshotter.v1.erofs' +EROFS_MARKER='# devcontainers-features:disable-erofs' + +if ! grep -qF "${EROFS_MARKER}" /etc/containerd/config.toml; then + if grep -qE '^[[:space:]]*disabled_plugins[[:space:]]*=' /etc/containerd/config.toml; then + # Add erofs URI to the existing top-level disabled_plugins list. + # Branches are mutually exclusive and guarded so we never produce + # duplicate entries on re-runs. + if grep -qE '^[[:space:]]*disabled_plugins[[:space:]]*=[[:space:]]*\[[[:space:]]*\]' /etc/containerd/config.toml; then + # disabled_plugins = [] -> insert URI as the only entry. + sed -i -E \ + "s|^([[:space:]]*disabled_plugins[[:space:]]*=[[:space:]]*\[)[[:space:]]*\]|\1\"${EROFS_PLUGIN_URI}\"]|" \ + /etc/containerd/config.toml + elif ! grep -qF "\"${EROFS_PLUGIN_URI}\"" /etc/containerd/config.toml; then + # disabled_plugins = ["existing", ...] -> append URI. + sed -i -E \ + "s|^([[:space:]]*disabled_plugins[[:space:]]*=[[:space:]]*\[)([^]]*[^],[:space:]])[[:space:]]*\]|\1\2, \"${EROFS_PLUGIN_URI}\"]|" \ + /etc/containerd/config.toml + fi + else + # No disabled_plugins key in the config: prepend one. + tmp_cfg="$(mktemp)" + { + printf 'disabled_plugins = ["%s"]\n\n' "${EROFS_PLUGIN_URI}" + cat /etc/containerd/config.toml + } > "${tmp_cfg}" + mv "${tmp_cfg}" /etc/containerd/config.toml + fi + # Idempotency marker + printf '\n%s\n' "${EROFS_MARKER}" >> /etc/containerd/config.toml +fi + if [ ! -d /usr/local/share ]; then mkdir -p /usr/local/share fi @@ -974,8 +1049,42 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU DEFAULT_ADDRESS_POOL="--default-address-pool $DOCKER_DEFAULT_ADDRESS_POOL" fi + + # Start our own containerd so it picks up /etc/containerd/config.toml + # (notably the disabled_plugins entry for the erofs snapshotter, see + # https://github.com/devcontainers/features/issues/1642). dockerd's + # built-in containerd child uses an auto-generated config that ignores + # /etc/containerd/config.toml, so we must run containerd ourselves and + # point dockerd at it via --containerd. + CONTAINERD_SOCK="/run/containerd/containerd.sock" + CONTAINERD_BIN="" + for candidate in /usr/local/bin/containerd /usr/bin/containerd /usr/sbin/containerd; do + if [ -x "$candidate" ]; then + CONTAINERD_BIN="$candidate" + break + fi + done + DOCKERD_CONTAINERD_ARG="" + if [ -n "$CONTAINERD_BIN" ] && [ -f /etc/containerd/config.toml ]; then + mkdir -p /run/containerd + if ! pgrep -x containerd > /dev/null 2>&1; then + ( "$CONTAINERD_BIN" --config /etc/containerd/config.toml > /tmp/containerd.log 2>&1 ) & + fi + # Wait up to ~5s for the socket to appear + i=0 + while [ $i -lt 50 ] && [ ! -S "$CONTAINERD_SOCK" ]; do + sleep 0.1 + i=$((i + 1)) + done + if [ -S "$CONTAINERD_SOCK" ]; then + DOCKERD_CONTAINERD_ARG="--containerd $CONTAINERD_SOCK" + else + echo "(*) containerd socket not ready; letting dockerd spawn its own containerd." + fi + fi + # Start docker/moby engine - ( dockerd $CUSTOMDNS $DEFAULT_ADDRESS_POOL $DOCKER_DEFAULT_IP6_TABLES > /tmp/dockerd.log 2>&1 ) & + ( dockerd $DOCKERD_CONTAINERD_ARG $CUSTOMDNS $DEFAULT_ADDRESS_POOL $DOCKER_DEFAULT_IP6_TABLES > /tmp/dockerd.log 2>&1 ) & INNEREOF )" diff --git a/test/docker-in-docker/dockerIp6tablesDisabledTest.sh b/test/docker-in-docker/dockerIp6tablesDisabledTest.sh index 977054ffc..5fe8c2a3b 100644 --- a/test/docker-in-docker/dockerIp6tablesDisabledTest.sh +++ b/test/docker-in-docker/dockerIp6tablesDisabledTest.sh @@ -16,7 +16,7 @@ ip6tablesCheck() { echo "โ•ip6tables command not found. โ•" fi } - +check "docker ps" bash -c "docker ps" check "ip6tables" ip6tablesCheck check "ip6tables check" bash -c "docker network inspect bridge" check "docker-build" docker build ./ diff --git a/test/docker-in-docker/docker_build_older.sh b/test/docker-in-docker/docker_build_older.sh index d60cd937a..ed9932a84 100644 --- a/test/docker-in-docker/docker_build_older.sh +++ b/test/docker-in-docker/docker_build_older.sh @@ -10,6 +10,6 @@ check "docker-buildx" docker buildx version check "docker-build" docker build ./ check "docker-buildx" bash -c "docker buildx version" check "docker-buildx-path" bash -c "ls -la /usr/libexec/docker/cli-plugins/docker-buildx" - +check "docker ps" bash -c "docker ps" # Report result reportResults diff --git a/test/docker-in-docker/pin_docker-ce_version_moby_false.sh b/test/docker-in-docker/pin_docker-ce_version_moby_false.sh index ec33d1504..4f1eedb84 100644 --- a/test/docker-in-docker/pin_docker-ce_version_moby_false.sh +++ b/test/docker-in-docker/pin_docker-ce_version_moby_false.sh @@ -5,6 +5,7 @@ source dev-container-features-test-lib check "docker-ce" bash -c "docker --version" check "docker-ce-cli" bash -c "docker version" +check "docker ps" bash -c "docker ps" #report result reportResults \ No newline at end of file From 6a28f2da32040c8c2dde2c83826b242a31567325 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Wed, 20 May 2026 16:24:33 +0530 Subject: [PATCH 54/78] [docker-in-docker] - Fixing issue with containerd (#1653) * [docker-in-docker] - Fixing issue with containerd * Change the base image used for stress test as its not compatible with ubuntu resolute * Change test base image --- .../docker-in-docker-stress-test.yaml | 4 +- .github/workflows/test-pr-arm64.yaml | 4 +- .github/workflows/test-pr.yaml | 4 +- src/docker-in-docker/NOTES.md | 11 +++++ .../devcontainer-feature.json | 7 +++- test/docker-in-docker/Dockerfile | 2 +- .../overlayfs_containerd_root.sh | 42 +++++++++++++++++++ test/docker-in-docker/scenarios.json | 11 ++++- 8 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 test/docker-in-docker/overlayfs_containerd_root.sh diff --git a/.github/workflows/docker-in-docker-stress-test.yaml b/.github/workflows/docker-in-docker-stress-test.yaml index 99470e558..a63225a13 100644 --- a/.github/workflows/docker-in-docker-stress-test.yaml +++ b/.github/workflows/docker-in-docker-stress-test.yaml @@ -19,7 +19,7 @@ jobs: run: npm install -g @devcontainers/cli - name: "Generating tests for 'docker-in-docker' which validates if docker daemon is running" - run: devcontainer features test --skip-scenarios -f docker-in-docker -i mcr.microsoft.com/devcontainers/base:ubuntu . + run: devcontainer features test --skip-scenarios -f docker-in-docker -i mcr.microsoft.com/devcontainers/base:noble . test-onCreate: strategy: @@ -34,4 +34,4 @@ jobs: run: npm install -g @devcontainers/cli - name: "Generating tests for 'docker-in-docker' which validates if docker daemon is available within 'onCreateCommand'" - run: devcontainer features test -f docker-in-docker --skip-autogenerated --filter "docker_with_on_create_command" \ No newline at end of file + run: devcontainer features test -f docker-in-docker --skip-autogenerated --filter "docker_with_on_create_command" -i mcr.microsoft.com/devcontainers/base:noble \ No newline at end of file diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index f05048e1b..e5855ced2 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -47,7 +47,9 @@ jobs: ] exclude: - features: docker-in-docker - baseImage: mcr.microsoft.com/devcontainers/base:debian + baseImage: mcr.microsoft.com/devcontainers/base:debian + - features: docker-in-docker + baseImage: mcr.microsoft.com/devcontainers/base:ubuntu steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index a10e94d3c..83944a5c8 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -65,7 +65,9 @@ jobs: - features: docker-in-docker baseImage: mcr.microsoft.com/devcontainers/base:debian - features: docker-outside-of-docker - baseImage: mcr.microsoft.com/devcontainers/base:debian + baseImage: mcr.microsoft.com/devcontainers/base:debian + - features: docker-in-docker + baseImage: mcr.microsoft.com/devcontainers/base:ubuntu steps: - uses: actions/checkout@v6 diff --git a/src/docker-in-docker/NOTES.md b/src/docker-in-docker/NOTES.md index 67f736f59..693afd41f 100644 --- a/src/docker-in-docker/NOTES.md +++ b/src/docker-in-docker/NOTES.md @@ -18,3 +18,14 @@ Debian Trixie (13) does not include moby-cli and related system packages, so the Ubuntu 26.04 (Resolute) does not currently have moby packages available, so the feature cannot install with "moby": "true". To use this feature on Resolute, please set "moby": "false". Additionally, the kernel on Ubuntu 26.04 no longer supports legacy iptables NAT tables, so the feature automatically falls back to `iptables-nft` when `iptables-legacy` is not functional. `bash` is required to execute the `install.sh` script. + +## Persisted state + +This Feature mounts two named Docker volumes into the dev container so that the daemons have writable, non-overlay storage for their state: + +* `dind-var-lib-docker-${devcontainerId}` โ†’ `/var/lib/docker` +* `dind-var-lib-containerd-${devcontainerId}` โ†’ `/var/lib/containerd` + +The `/var/lib/containerd` mount is required when the dev container's root filesystem is itself an overlayfs mount (the default in Kubernetes / containerd-backed hosts, GitHub Codespaces, and Docker with the containerd image store enabled). Without it, the standalone `containerd` started by this Feature would place its overlayfs snapshotter data on an overlay rootfs, causing overlay-on-overlay mounts to fail with `invalid argument`. See [issue #1639](https://github.com/devcontainers/features/issues/1639) for background. + +Because both volumes are scoped to `${devcontainerId}`, each dev container gets its own state and rebuilds preserve images and snapshots. Removing the dev container does not automatically remove these volumes; clean them up with `docker volume rm` if you want to reclaim space. diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 406627f7e..0af78923e 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": "3.0.0", + "version": "3.0.1", "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.", @@ -86,6 +86,11 @@ "source": "dind-var-lib-docker-${devcontainerId}", "target": "/var/lib/docker", "type": "volume" + }, + { + "source": "dind-var-lib-containerd-${devcontainerId}", + "target": "/var/lib/containerd", + "type": "volume" } ], "installsAfter": [ diff --git a/test/docker-in-docker/Dockerfile b/test/docker-in-docker/Dockerfile index 9cc9f1a39..f2ca69d1d 100644 --- a/test/docker-in-docker/Dockerfile +++ b/test/docker-in-docker/Dockerfile @@ -1 +1 @@ -FROM ubuntu:focal +FROM ubuntu:noble diff --git a/test/docker-in-docker/overlayfs_containerd_root.sh b/test/docker-in-docker/overlayfs_containerd_root.sh new file mode 100644 index 000000000..f1099c91e --- /dev/null +++ b/test/docker-in-docker/overlayfs_containerd_root.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Regression test for devcontainers/features#1639 / PR #1645 follow-up: +# verifies that when the dev container's root filesystem is overlayfs +# (the default under Docker / containerd-backed hosts), the standalone +# containerd started by the docker-in-docker Feature does NOT place its +# overlayfs snapshotter data on an overlay rootfs (which would fail with +# `invalid argument` when pulling images). +# +set -e + +source dev-container-features-test-lib + +# 1. Confirm we're really reproducing the affected condition: +# the dev container's / must be overlay. +check "rootfs is overlay (precondition)" \ + bash -c '[ "$(findmnt -no FSTYPE /)" = "overlay" ]' + +# 2. The Feature's volume mount must shadow /var/lib/containerd with a +# non-overlay filesystem. Without the mount, containerd's overlayfs +# snapshotter would be writing onto the overlay rootfs and fail at +# pull time. +check "/var/lib/containerd is not overlay" \ + bash -c '[ "$(findmnt -no FSTYPE /var/lib/containerd)" != "overlay" ]' + +check "/var/lib/docker is not overlay" \ + bash -c '[ "$(findmnt -no FSTYPE /var/lib/docker)" != "overlay" ]' + +# 3. The actual symptom: pulling and running an image must succeed. +# Pre-PR-#1645 this fails with: +# failed to mount /tmp/containerd-mountXXXXX ... err: invalid argument +check "docker run hello-world" \ + docker run --rm hello-world + +# 4. Belt-and-braces: confirm dockerd is actually using the +# containerd-snapshotter path so we know this test exercises the +# affected code path, not the legacy overlay2 driver. +check "containerd-snapshotter active" \ + bash -c "docker info 2>/dev/null | grep -qiE 'driver-type: io.containerd.snapshotter.v1|Storage Driver: overlayfs'" + +reportResults + diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index ad8c2ce48..2f9df3958 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -1,4 +1,13 @@ { + "overlayfs_containerd_root": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "docker-in-docker": { + "version": "latest", + "moby": true + } + } + }, "docker_build_fallback_compose": { "image": "ubuntu:noble", "features": { @@ -108,7 +117,7 @@ } }, "docker_python_bookworm": { - "image": "mcr.microsoft.com/devcontainers/base:bookworm", + "image": "mcr.microsoft.com/devcontainers/base:2.1.8-bookworm", "features": { "docker-in-docker": { "moby": true, From f5bfe123db12af60b3b20299b60c123c56614e4a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 12:00:35 +0100 Subject: [PATCH 55/78] Fix Terraform Sentinel prerelease version resolution in scenario tests (#1648) * Initial plan * fix(terraform): preserve sentinel prerelease version names * chore(terraform): bump feature minor version * fix(terraform): correct feature patch version bump --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska --- src/terraform/devcontainer-feature.json | 2 +- src/terraform/install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index a72f18993..f9ebcbee8 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.4.2", + "version": "1.4.3", "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 999815a38..8c4755ebe 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -249,7 +249,7 @@ find_sentinel_version_from_url() { if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then local prefix='sentinel_' local regex="${prefix}\d.\d{2}.\d(?:-\w*)?" - local version_list="$(wget -q $2 -O - | grep -oP ${regex} | tr -d ${prefix} | sort -rV)" + local version_list="$(wget -q $2 -O - | grep -oP ${regex} | sed "s/^${prefix}//" | sort -rV)" if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" else From ca1c1661e324b7b5d86b407b28a08687d2c3d759 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 18:04:59 +0100 Subject: [PATCH 56/78] fix: make bubblewrap conditionally installed for RedHat-based images (#1651) * Initial plan * fix: make bubblewrap conditionally installed on RedHat-based systems bubblewrap is not available in UBI repositories, causing common-utils installation to fail on UBI base images. This change checks package availability before adding bubblewrap to the install list, following the same pattern used for compat-openssl10 and redhat-lsb-core. Also adds a test scenario for UBI 8 image. * chore: bump common-utils version to 2.5.9 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 6 +++++- test/common-utils/scenarios.json | 7 +++++++ test/common-utils/ubi-8.sh | 16 ++++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 test/common-utils/ubi-8.sh diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 92d47fc3c..5b56f6184 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.5.8", + "version": "2.5.9", "name": "Common Utilities", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/common-utils", "description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.", diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index eab75ced3..5e5487aa2 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -216,9 +216,13 @@ install_redhat_packages() { which \ man-db \ strace \ - bubblewrap \ socat" + # Install bubblewrap if available (not present in UBI repositories) + if ${install_cmd} -q list bubblewrap >/dev/null 2>&1; then + package_list="${package_list} bubblewrap" + fi + # rockylinux:9 installs 'curl-minimal' which clashes with 'curl' # Install 'curl' for every OS except this rockylinux:9 if [[ "${ID}" = "rocky" ]] && [[ "${VERSION}" != *"9."* ]]; then diff --git a/test/common-utils/scenarios.json b/test/common-utils/scenarios.json index ee138ca22..c70c574a7 100644 --- a/test/common-utils/scenarios.json +++ b/test/common-utils/scenarios.json @@ -284,5 +284,12 @@ "features": { "common-utils": {} } + }, + "ubi-8": { + "image": "registry.access.redhat.com/ubi8/ubi:8.10", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } } } \ No newline at end of file diff --git a/test/common-utils/ubi-8.sh b/test/common-utils/ubi-8.sh new file mode 100644 index 000000000..d97e614a7 --- /dev/null +++ b/test/common-utils/ubi-8.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${PLATFORM_ID}" = "platform:el8" +check "curl" curl --version +check "jq" jq --version + +# Report result +reportResults From 0c44debb78548dd9c15258b51efafede9bf53c78 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 18:24:11 +0100 Subject: [PATCH 57/78] Add Ubuntu 26.04 (resolute) support to docker-outside-of-docker (#1656) * Initial plan * Add Ubuntu 26.04 (resolute) support to docker-outside-of-docker - Add "resolute" to DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES - Block moby on resolute (packages not available), matching docker-in-docker behavior - Add test scenario for Ubuntu resolute with moby=false * Bump version to 1.10.0 and exclude base:ubuntu from test-pr workflow - Bump docker-outside-of-docker version from 1.9.1 to 1.10.0 - Add exclusion for docker-outside-of-docker with base:ubuntu image in test-pr workflow since moby (default) is not available on resolute * Remove trailing whitespace in test-pr.yaml * Add note about Ubuntu 26.04 requiring moby: false in NOTES.md --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .github/workflows/test-pr.yaml | 6 ++++-- src/docker-outside-of-docker/NOTES.md | 10 ++++++++++ .../devcontainer-feature.json | 2 +- src/docker-outside-of-docker/install.sh | 8 ++++---- .../install_on_ubuntu_resolute.sh | 12 ++++++++++++ test/docker-outside-of-docker/scenarios.json | 8 ++++++++ 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 test/docker-outside-of-docker/install_on_ubuntu_resolute.sh diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 83944a5c8..e00c50876 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -65,9 +65,11 @@ jobs: - features: docker-in-docker baseImage: mcr.microsoft.com/devcontainers/base:debian - features: docker-outside-of-docker - baseImage: mcr.microsoft.com/devcontainers/base:debian + baseImage: mcr.microsoft.com/devcontainers/base:debian - features: docker-in-docker - baseImage: mcr.microsoft.com/devcontainers/base:ubuntu + baseImage: mcr.microsoft.com/devcontainers/base:ubuntu + - features: docker-outside-of-docker + baseImage: mcr.microsoft.com/devcontainers/base:ubuntu steps: - uses: actions/checkout@v6 diff --git a/src/docker-outside-of-docker/NOTES.md b/src/docker-outside-of-docker/NOTES.md index ca6f43114..f9d7dbc06 100644 --- a/src/docker-outside-of-docker/NOTES.md +++ b/src/docker-outside-of-docker/NOTES.md @@ -60,4 +60,14 @@ This Feature should work on recent versions of Debian/Ubuntu-based distributions Debian Trixie (13) does not include moby-cli and related system packages, so the feature cannot install with "moby": "true". To use this feature on Trixie, please set "moby": "false" or choose a different base image (for example, Ubuntu 24.04). +Ubuntu 26.04 LTS (Resolute) does not have moby-cli packages available, so the feature only supports installation with `"moby": false`. To use this feature on Ubuntu 26.04, set `"moby": false` in your feature configuration: + +```json +"features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "moby": false + } +} +``` + `bash` is required to execute the `install.sh` script. diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index d98b8021c..3cf3513c0 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.9.1", + "version": "1.10.0", "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 4799a4d59..ae4c7ae51 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -22,7 +22,7 @@ INSTALL_DOCKER_COMPOSE_SWITCH="${INSTALLDOCKERCOMPOSESWITCH:-"true"}" MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" MICROSOFT_GPG_KEYS_ROLLING_URI="https://packages.microsoft.com/keys/microsoft-rolling.asc" DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal jammy noble plucky" -DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal hirsute impish jammy noble plucky" +DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal hirsute impish jammy noble plucky resolute" set -e @@ -207,9 +207,9 @@ fi # Fetch host/container arch. architecture="$(dpkg --print-architecture)" -# Prevent attempting to install Moby on Debian trixie (packages removed) -if [ "${USE_MOBY}" = "true" ] && [ "${ID}" = "debian" ] && [ "${VERSION_CODENAME}" = "trixie" ]; then - err "The 'moby' option is not supported on Debian 'trixie' because 'moby-cli' and related system packages have been removed from that distribution." +# Prevent attempting to install Moby on Debian trixie or Ubuntu resolute (packages not available) +if [ "${USE_MOBY}" = "true" ] && ([ "${VERSION_CODENAME}" = "trixie" ] || [ "${VERSION_CODENAME}" = "resolute" ]); then + err "The 'moby' option is not supported on ${ID} '${VERSION_CODENAME}' because 'moby-cli' and related system packages are not available in that distribution." err "To continue, either set the feature option '\"moby\": false' or use a different base image (for example: 'debian:bookworm' or 'ubuntu-24.04')." exit 1 fi diff --git a/test/docker-outside-of-docker/install_on_ubuntu_resolute.sh b/test/docker-outside-of-docker/install_on_ubuntu_resolute.sh new file mode 100644 index 000000000..c6c679684 --- /dev/null +++ b/test/docker-outside-of-docker/install_on_ubuntu_resolute.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "docker installed" bash -c "type docker" + +# Report results +reportResults diff --git a/test/docker-outside-of-docker/scenarios.json b/test/docker-outside-of-docker/scenarios.json index 61b94f3d6..2df748169 100644 --- a/test/docker-outside-of-docker/scenarios.json +++ b/test/docker-outside-of-docker/scenarios.json @@ -181,6 +181,14 @@ } } }, + "install_on_ubuntu_resolute": { + "image": "ubuntu:resolute", + "features": { + "docker-outside-of-docker": { + "moby": false + } + } + }, "rootless_docker_socket": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", "features": { From 8bd6ad7cbd5095dea611ef4bb901bb05d494917e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 17:03:58 +0100 Subject: [PATCH 58/78] fix(copilot-cli): use semver sort for prerelease tag resolution (#1657) * Initial plan * fix(copilot-cli): use semver sort for prerelease tag resolution * refactor(copilot-cli): extract resolve_prerelease_version function and add test Extract the prerelease tag resolution logic into a standalone resolve_prerelease_version() function that can read from stdin for testing. Add a scenario test that validates version sorting with mock git ls-remote data to prevent regressions. * fix(copilot-cli): make repo_url mandatory in resolve_prerelease_version Remove the stdin/cat fallback and use ${1:?} to error if no URL is provided. Update the test to mock git via PATH instead of piping stdin. * Add tests as per review comments without git ls and separate test for prerelease tag * Further changes in the test and code cleanup --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska --- src/copilot-cli/devcontainer-feature.json | 2 +- src/copilot-cli/install.sh | 12 ++++++- test/copilot-cli/install_prerelease.sh | 19 +++++++++++ .../copilot-cli/resolve_prerelease_version.sh | 32 +++++++++++++++++++ test/copilot-cli/scenarios.json | 18 +++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 test/copilot-cli/install_prerelease.sh create mode 100644 test/copilot-cli/resolve_prerelease_version.sh create mode 100644 test/copilot-cli/scenarios.json diff --git a/src/copilot-cli/devcontainer-feature.json b/src/copilot-cli/devcontainer-feature.json index bfd97286c..f4db33ced 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.2", + "version": "1.1.3", "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 47c2e3aca..ad4a19701 100755 --- a/src/copilot-cli/install.sh +++ b/src/copilot-cli/install.sh @@ -31,6 +31,14 @@ 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 +} + download_from_github() { local release_url=$1 echo "Downloading GitHub Copilot CLI from ${release_url}..." @@ -63,8 +71,10 @@ 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="$(git ls-remote --tags https://github.com/github/copilot-cli | tail -1 | awk -F/ '{print $NF}')" + + 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}" + else # Install specific version # Add leading v to version if it doesn't start with v diff --git a/test/copilot-cli/install_prerelease.sh b/test/copilot-cli/install_prerelease.sh new file mode 100644 index 000000000..6ef707418 --- /dev/null +++ b/test/copilot-cli/install_prerelease.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# End-to-end check that the "prerelease" channel actually resolves a tag and +# installs the binary. Regression guard for the inline pipeline in +# src/copilot-cli/install.sh. + +check "copilot binary is on PATH" which copilot +check "copilot reports a version" bash -c "copilot -v" + +# Auto-update flag file must exist for prerelease channel. +check "auto-update flag created for prerelease" test -f /etc/devcontainer-copilot-cli/auto-update + +# Report result +reportResults diff --git a/test/copilot-cli/resolve_prerelease_version.sh b/test/copilot-cli/resolve_prerelease_version.sh new file mode 100644 index 000000000..aa4ff41fd --- /dev/null +++ b/test/copilot-cli/resolve_prerelease_version.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +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 +} + +# Tests the tag-resolution pipeline used by src/copilot-cli/install.sh for the +# "prerelease" channel. + +check "copilot binary is on PATH" which copilot +check "copilot reports a version" bash -c "copilot -v" + +result1="$(resolve_prerelease_version $'abc1234\trefs/tags/v1.0.1\ndef5678\trefs/tags/v1.0.9\nghi9012\trefs/tags/v1.0.10\njkl3456\trefs/tags/v1.0.45\nmno7890\trefs/tags/v1.0.2\n')" +check "picks highest version (v1.0.45)" bash -c "[ '${result1}' = 'v1.0.45' ]" + +result2="$(resolve_prerelease_version $'abc1234\trefs/tags/v1.0.44\ndef5678\trefs/tags/v1.0.45-1\nghi9012\trefs/tags/v1.0.45-10\njkl3456\trefs/tags/v1.0.45-2\nmno7890\trefs/tags/v1.0.45\n')" +check "picks highest prerelease (v1.0.45-10)" bash -c "[ '${result2}' = 'v1.0.45-10' ]" + +result3="$(resolve_prerelease_version $'abc1234\trefs/tags/latest\ndef5678\trefs/tags/v1.0.3\nghi9012\trefs/tags/nightly\njkl3456\trefs/tags/v1.0.20\n')" +check "picks highest version ignoring non-version tags (v1.0.20)" bash -c "[ '${result3}' = 'v1.0.20' ]" + +# Report result +reportResults diff --git a/test/copilot-cli/scenarios.json b/test/copilot-cli/scenarios.json new file mode 100644 index 000000000..3d8336bec --- /dev/null +++ b/test/copilot-cli/scenarios.json @@ -0,0 +1,18 @@ +{ + "resolve_prerelease_version": { + "image": "ubuntu:noble", + "features": { + "copilot-cli": { + "version": "latest" + } + } + }, + "install_prerelease": { + "image": "ubuntu:noble", + "features": { + "copilot-cli": { + "version": "prerelease" + } + } + } +} From 72df8a5f191f840a66dc2e2ced10a136e4d75173 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 17:06:22 +0100 Subject: [PATCH 59/78] fix: azure-cli falls back to pip on unknown Ubuntu codenames (#1668) * Initial plan * fix: azure-cli falls back to pip on unknown codenames, add resolute support - Add 'resolute' (Ubuntu 26.04) to AZCLI_ARCHIVE_VERSION_CODENAMES - Add else branch to fall back to pip when codename/arch not in allowlist - Bump feature version to 1.3.0 - Add test scenario for Ubuntu resolute * test: add scenario for pip fallback on Ubuntu plucky (25.04) Ubuntu plucky's codename is not in the apt archive allowlist, so this scenario exercises the else-branch pip fallback path. * Correcting the fallback test --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kaniska --- src/azure-cli/devcontainer-feature.json | 2 +- src/azure-cli/install.sh | 5 ++++- .../install_fallback_pip_ubuntu_plucky.sh | 13 +++++++++++++ test/azure-cli/install_in_ubuntu_resolute.sh | 14 ++++++++++++++ test/azure-cli/scenarios.json | 17 +++++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/azure-cli/install_fallback_pip_ubuntu_plucky.sh create mode 100644 test/azure-cli/install_in_ubuntu_resolute.sh diff --git a/src/azure-cli/devcontainer-feature.json b/src/azure-cli/devcontainer-feature.json index a634818b6..7280ad2ad 100644 --- a/src/azure-cli/devcontainer-feature.json +++ b/src/azure-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "azure-cli", - "version": "1.2.9", + "version": "1.3.0", "name": "Azure CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/azure-cli", "description": "Installs the Azure CLI along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.", diff --git a/src/azure-cli/install.sh b/src/azure-cli/install.sh index bc5744ce2..425deafe9 100755 --- a/src/azure-cli/install.sh +++ b/src/azure-cli/install.sh @@ -19,7 +19,7 @@ AZ_BICEPVERSION=${BICEPVERSION:-latest} INSTALL_USING_PYTHON=${INSTALLUSINGPYTHON:-false} MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" AZCLI_ARCHIVE_ARCHITECTURES="amd64 arm64" -AZCLI_ARCHIVE_VERSION_CODENAMES="stretch bookworm buster bullseye bionic focal jammy noble trixie" +AZCLI_ARCHIVE_VERSION_CODENAMES="stretch bookworm buster bullseye bionic focal jammy noble trixie resolute" 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.' @@ -200,6 +200,9 @@ CACHED_AZURE_VERSION="${AZ_VERSION}" # In case we need to fallback to pip and th if [ "${INSTALL_USING_PYTHON}" != "true" ]; then if [[ "${AZCLI_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[ "${AZCLI_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then install_using_apt || use_pip="true" + else + echo "(*) Codename '${VERSION_CODENAME}' or architecture '${architecture}' not in apt archive list, falling back to pip installation." + use_pip="true" fi else use_pip="true" diff --git a/test/azure-cli/install_fallback_pip_ubuntu_plucky.sh b/test/azure-cli/install_fallback_pip_ubuntu_plucky.sh new file mode 100644 index 000000000..f6fffd02b --- /dev/null +++ b/test/azure-cli/install_fallback_pip_ubuntu_plucky.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Ubuntu plucky (25.04) is NOT in the apt archive codename allowlist, +# so this test validates the pip fallback path. +check "version" az --version + +# Report result +reportResults diff --git a/test/azure-cli/install_in_ubuntu_resolute.sh b/test/azure-cli/install_in_ubuntu_resolute.sh new file mode 100644 index 000000000..eff30a25c --- /dev/null +++ b/test/azure-cli/install_in_ubuntu_resolute.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Check to make sure the user is vscode +check "user is vscode" whoami | grep vscode + +check "version" az --version + +# Report result +reportResults diff --git a/test/azure-cli/scenarios.json b/test/azure-cli/scenarios.json index 1ba173de9..29fb4f935 100644 --- a/test/azure-cli/scenarios.json +++ b/test/azure-cli/scenarios.json @@ -1,4 +1,12 @@ { + "install_fallback_pip_ubuntu_plucky": { + "image": "ubuntu:plucky", + "features": { + "azure-cli": { + "version": "latest" + } + } + }, "install_extensions_trixie": { "image": "mcr.microsoft.com/devcontainers/base:trixie", "user": "vscode", @@ -102,5 +110,14 @@ "moby": false } } + }, + "install_in_ubuntu_resolute": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "user": "vscode", + "features": { + "azure-cli": { + "version": "latest" + } + } } } \ No newline at end of file From 7ae907dc1a8f19b961a0f36c88d28fd025c025ba Mon Sep 17 00:00:00 2001 From: Kaniska Date: Wed, 10 Jun 2026 23:26:20 +0530 Subject: [PATCH 60/78] [php] - Install xdebug from source if installation fails with PECL (#1671) --- src/php/devcontainer-feature.json | 6 ++--- src/php/install.sh | 36 ++++++++++++++++++++++++++++-- test/php/install_additional_php.sh | 5 ++--- test/php/scenarios.json | 5 +++-- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/php/devcontainer-feature.json b/src/php/devcontainer-feature.json index 4db478230..6abdc965b 100644 --- a/src/php/devcontainer-feature.json +++ b/src/php/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "php", - "version": "1.1.4", + "version": "1.1.5", "name": "PHP", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/php", "options": { @@ -9,8 +9,8 @@ "proposals": [ "latest", "8", - "8.2", - "8.2.0", + "8.5", + "8.5.0", "none" ], "default": "latest", diff --git a/src/php/install.sh b/src/php/install.sh index 357395e88..531b16518 100755 --- a/src/php/install.sh +++ b/src/php/install.sh @@ -170,6 +170,31 @@ addcomposer() { "${PHP_SRC}" -r "unlink('composer-setup.php');" } +# Build xdebug from its official source tarball. Used as a fallback when +# pecl.php.net is broken or has no release advertising compatibility with +# 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/" + + local xdebug_src_dir="/tmp/xdebug-src" + rm -rf "${xdebug_src_dir}" + mkdir -p "${xdebug_src_dir}" + + wget -O /tmp/xdebug.tgz "https://xdebug.org/files/xdebug-${XDEBUG_VERSION}.tgz" + tar -xzf /tmp/xdebug.tgz -C "${xdebug_src_dir}" --strip-components=1 + + ( + cd "${xdebug_src_dir}" + "${PHP_INSTALL_DIR}/bin/phpize" + ./configure --enable-xdebug --with-php-config="${PHP_INSTALL_DIR}/bin/php-config" + make -j "$(nproc)" + make install + ) + + rm -rf "${xdebug_src_dir}" /tmp/xdebug.tgz +} + init_php_install() { PHP_INSTALL_DIR="${PHP_DIR}/${PHP_VERSION}" if [ -d "${PHP_INSTALL_DIR}" ]; then @@ -219,8 +244,10 @@ install_php() { # PHP 7.4+, the pecl/pear installers are officially deprecated and are removed in PHP 8+ # Thus, requiring an explicit "--with-pear" + OLDIFS=$IFS IFS="." read -a versions <<< "${PHP_VERSION}" + IFS=$OLDIFS PHP_MAJOR_VERSION=${versions[0]} PHP_MINOR_VERSION=${versions[1]} @@ -240,8 +267,13 @@ install_php() { cp -v $PHP_SRC_DIR/php.ini-* "$PHP_INI_DIR/"; cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" - # Install xdebug - "${PHP_INSTALL_DIR}/bin/pecl" install xdebug + # Install xdebug. Try PECL first (fast path), then fall back to building + # from source if PECL's channel cache is broken or no release advertises + # compatibility with the current PHP version. + "${PHP_INSTALL_DIR}/bin/pecl" channel-update pecl.php.net || true + if ! "${PHP_INSTALL_DIR}/bin/pecl" install xdebug; then + install_xdebug_from_source + fi XDEBUG_INI="${CONF_DIR}/xdebug.ini" echo "zend_extension=${PHP_EXT_DIR}/xdebug.so" > "${XDEBUG_INI}" diff --git a/test/php/install_additional_php.sh b/test/php/install_additional_php.sh index c1c085b5c..b5bdcace2 100644 --- a/test/php/install_additional_php.sh +++ b/test/php/install_additional_php.sh @@ -5,9 +5,8 @@ set -e # Optional: Import test library source dev-container-features-test-lib -check "php version 8.4.2 installed as default" php --version | grep 8.4.2 -check "php version 8.3.14 installed" ls -l /usr/local/php | grep 8.3.14 -check "php version 8.2.27 installed" ls -l /usr/local/php | grep 8.2.27 +check "php version 8.5.0 installed as default" php --version | grep 8.5.0 +check "php version 8.4.15 installed" ls -l /usr/local/php | grep 8.4.15 check "composer-version" composer --version diff --git a/test/php/scenarios.json b/test/php/scenarios.json index 8aa25cbff..57abdf16d 100644 --- a/test/php/scenarios.json +++ b/test/php/scenarios.json @@ -3,8 +3,9 @@ "image": "ubuntu:noble", "features": { "php": { - "version": "8.4.2", - "additionalVersions": "8.3.14,8.2.27" + "version": "8.5.0", + "additionalVersions": "8.4.15", + "installComposer": "true" } } }, From a8f0b45732c6b170aa48d9276c01da13be9d6e71 Mon Sep 17 00:00:00 2001 From: Brian Helba Date: Mon, 15 Jun 2026 08:31:24 -0400 Subject: [PATCH 61/78] [node] - Install pnpm as non-root user to prevent root-owned npm cache (#1625) * [node] - Install pnpm as non-root user to prevent root-owned npm cache Currently, the pnpm installation block runs `npm install -g pnpm` in a bare subshell as root, unlike every other npm/nvm operation in the script which uses `su ${USERNAME}`. This causes the npm cache directory to be created owned by `root:root`, leading to `EACCES` errors for the non-root user on subsequent npm operations. This is particularly reproducible on macOS with Rosetta 2 emulation, where the cache directory may not already exist from prior steps. Note, the explicit setting of proxy env vars (`http_proxy`, `https_proxy`, `no_proxy`) is likely a workaround for https://github.com/npm/cli/issues/6835. No other commands in this script use that workaround anymore, so this change uses the same `su` syntax as all other commands. * Bump Node.js version to 2.1.0 --------- Co-authored-by: Abdurrahmaan Iqbal --- src/node/devcontainer-feature.json | 2 +- src/node/install.sh | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 2e86262ae..5e0970b2d 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "2.0.0", + "version": "2.1.0", "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 20ea85463..0e277812e 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -482,13 +482,7 @@ if [ ! -z "${PNPM_VERSION}" ] && [ "${PNPM_VERSION}" = "none" ]; then echo "Ignoring installation of PNPM" else if bash -c ". '${NVM_DIR}/nvm.sh' && type npm >/dev/null 2>&1"; then - ( - . "${NVM_DIR}/nvm.sh" - [ ! -z "$http_proxy" ] && npm set proxy="$http_proxy" - [ ! -z "$https_proxy" ] && npm set https-proxy="$https_proxy" - [ ! -z "$no_proxy" ] && npm set noproxy="$no_proxy" - npm install -g pnpm@$PNPM_VERSION --force - ) + su ${USERNAME} -c "umask 0002 && . '${NVM_DIR}/nvm.sh' && npm install -g pnpm@${PNPM_VERSION} --force" else echo "Skip installing pnpm because npm is missing" fi From f851b4860e3e40842bfc4053c0fb45b5a7d91c9a Mon Sep 17 00:00:00 2001 From: Kaniska Date: Wed, 17 Jun 2026 22:22:11 +0530 Subject: [PATCH 62/78] [Docker-in-Docker] - Update docker-compose to the latest version (#1672) * [Docker-in-Docker] - Update docker-compose to the latest version * Implement review comment. --- src/docker-in-docker/README.md | 2 +- .../devcontainer-feature.json | 7 ++++--- src/docker-in-docker/install.sh | 2 +- .../docker_compose_latest_moby.sh | 16 +++++++++++++++ .../docker_compose_latest_no_moby.sh | 14 +++++++++++++ test/docker-in-docker/scenarios.json | 20 +++++++++++++++++++ 6 files changed, 56 insertions(+), 5 deletions(-) create mode 100755 test/docker-in-docker/docker_compose_latest_moby.sh create mode 100644 test/docker-in-docker/docker_compose_latest_no_moby.sh diff --git a/src/docker-in-docker/README.md b/src/docker-in-docker/README.md index c58283317..f94c9be51 100644 --- a/src/docker-in-docker/README.md +++ b/src/docker-in-docker/README.md @@ -18,7 +18,7 @@ Create child containers *inside* a container, independent from the host's docker | version | Select or enter a Docker/Moby Engine version. (Availability can vary by OS version.) | string | latest | | moby | Install OSS Moby build instead of Docker CE | boolean | true | | mobyBuildxVersion | Install a specific version of moby-buildx when using Moby | string | latest | -| dockerDashComposeVersion | Default version of Docker Compose (v1, v2 or none) | string | v2 | +| dockerDashComposeVersion | Default version of Docker Compose (v1, v2, latest or none) | string | latest | | azureDnsAutoDetection | Allow automatically setting the dockerd DNS server when the installation script detects it is running in Azure | boolean | true | | dockerDefaultAddressPool | Define default address pools for Docker networks. e.g. base=192.168.0.0/16,size=24 | string | - | | installDockerBuildx | Install Docker Buildx | boolean | true | diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 0af78923e..6d7c0431a 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": "3.0.1", + "version": "3.1.0", "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.", @@ -29,11 +29,12 @@ "type": "string", "enum": [ "none", + "latest", "v1", "v2" ], - "default": "v2", - "description": "Default version of Docker Compose (v1, v2 or none)" + "default": "latest", + "description": "Default version of Docker Compose (v1, v2, latest or none)" }, "azureDnsAutoDetection": { "type": "boolean", diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index e9740efc5..70a187e28 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -11,7 +11,7 @@ DOCKER_VERSION="${VERSION:-"latest"}" # The Docker/Moby Engine + CLI should match in version USE_MOBY="${MOBY:-"true"}" MOBY_BUILDX_VERSION="${MOBYBUILDXVERSION:-"latest"}" -DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v2"}" #v1, v2 or none +DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"latest"}" #v1, v2, latest or none AZURE_DNS_AUTO_DETECTION="${AZUREDNSAUTODETECTION:-"true"}" DOCKER_DEFAULT_ADDRESS_POOL="${DOCKERDEFAULTADDRESSPOOL:-""}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" diff --git a/test/docker-in-docker/docker_compose_latest_moby.sh b/test/docker-in-docker/docker_compose_latest_moby.sh new file mode 100755 index 000000000..bd5a9d23f --- /dev/null +++ b/test/docker-in-docker/docker_compose_latest_moby.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "docker compose" bash -c "docker compose version | grep -E '[0-9]+\.[0-9]+\.[0-9]+'" +check "docker-compose" bash -c "docker-compose --version | grep -E '[0-9]+\.[0-9]+\.[0-9]+'" +check "installs compose as docker-compose" bash -c "[[ -f /usr/local/bin/docker-compose ]]" +check "moby-engine" bash -c "dpkg-query -W moby-engine" +check "moby-cli" bash -c "dpkg-query -W moby-cli" + +# Report result +reportResults diff --git a/test/docker-in-docker/docker_compose_latest_no_moby.sh b/test/docker-in-docker/docker_compose_latest_no_moby.sh new file mode 100644 index 000000000..5eeb34b45 --- /dev/null +++ b/test/docker-in-docker/docker_compose_latest_no_moby.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "docker compose" bash -c "docker compose version | grep -E '[0-9]+\.[0-9]+\.[0-9]+'" +check "docker-compose" bash -c "docker-compose --version | grep -E '[0-9]+\.[0-9]+\.[0-9]+'" +check "installs compose as docker-compose" bash -c "[[ -f /usr/local/bin/docker-compose ]]" + +# Report result +reportResults diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index 2f9df3958..a93495b51 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -146,6 +146,26 @@ } } }, + "docker_compose_latest_moby": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "docker-in-docker": { + "moby": true, + "installDockerBuildx": true, + "dockerDashComposeVersion": "latest" + } + } + }, + "docker_compose_latest_no_moby": { + "image": "mcr.microsoft.com/devcontainers/base:noble", + "features": { + "docker-in-docker": { + "moby": false, + "installDockerBuildx": true, + "dockerDashComposeVersion": "latest" + } + } + }, "docker_build_fallback_buildx": { "image": "ubuntu:noble", "features": { From 71d6d23dfb14f3bbae3de4080034002d57215adf Mon Sep 17 00:00:00 2001 From: Ryosuke Hiroe Date: Sat, 20 Jun 2026 02:24:36 +0900 Subject: [PATCH 63/78] feat(ruby)!: rewrite to use ruby-build with optional rbenv/rvm (v2.0.0) (#1654) * feat(ruby)!: rewrite to use ruby-build with optional rbenv/rvm (v2.0.0) Replace the rvm-only install with ruby-build under /usr/local/rubies, exposed via the 'current' symlink on the PATH. Add a versionManager option ("none" | "rbenv" | "rvm") that installs the chosen manager and delegates 'install' to it. Detect build deps across apt, dnf/yum, apk, zypper, and pacman. Replace ruby_fallback_test with ruby_rbenv / ruby_rvm scenarios. * test(ruby): symlink install_additional_ruby_trixie.sh to install_additional_ruby.sh --------- Co-authored-by: Kaniska --- src/ruby/NOTES.md | 9 +- src/ruby/README.md | 9 +- src/ruby/devcontainer-feature.json | 24 +- src/ruby/install.sh | 584 ++++++++++---------- test/ruby/install_additional_ruby.sh | 10 +- test/ruby/install_additional_ruby_trixie.sh | 18 +- test/ruby/install_ruby_trixie_base.sh | 8 +- test/ruby/ruby_fallback_test.sh | 310 ----------- test/ruby/ruby_rbenv.sh | 19 + test/ruby/ruby_rvm.sh | 19 + test/ruby/scenarios.json | 26 +- 11 files changed, 390 insertions(+), 646 deletions(-) mode change 100644 => 120000 test/ruby/install_additional_ruby_trixie.sh delete mode 100644 test/ruby/ruby_fallback_test.sh create mode 100755 test/ruby/ruby_rbenv.sh create mode 100755 test/ruby/ruby_rvm.sh diff --git a/src/ruby/NOTES.md b/src/ruby/NOTES.md index 19fe92f31..53da243c1 100644 --- a/src/ruby/NOTES.md +++ b/src/ruby/NOTES.md @@ -2,6 +2,13 @@ ## OS Support -This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. +This Feature supports Linux images that ship one of the following package managers: `apt`, `dnf`/`yum`, `apk`, `zypper`, or `pacman`. The script detects the available package manager and installs the build dependencies that ruby-build needs. `bash` is required to execute the `install.sh` script. + +## Layout + +- Ruby is installed under `/usr/local/rubies/` by ruby-build. +- The default Ruby is exposed via the `/usr/local/rubies/current` symlink, which is placed on the `PATH` through `containerEnv`. +- `ruby-build` itself is cloned to `/usr/local/share/ruby-build` and symlinked into `/usr/local/bin/ruby-build` so additional versions can be installed later. +- A shared `ruby` group owns `/usr/local/rubies`; the configured non-root user is added to it so `gem install` can write into the active Ruby tree without `sudo`. diff --git a/src/ruby/README.md b/src/ruby/README.md index 7df6965c7..351ffe906 100644 --- a/src/ruby/README.md +++ b/src/ruby/README.md @@ -1,13 +1,13 @@ -# Ruby (via rvm) (ruby) +# Ruby (via ruby-build) (ruby) -Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies. +Installs Ruby using ruby-build, with optional rbenv or rvm for version management. ## Example Usage ```json "features": { - "ghcr.io/devcontainers/features/ruby:1": {} + "ghcr.io/devcontainers/features/ruby:2": {} } ``` @@ -16,6 +16,7 @@ Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies. | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| | version | Select or enter a Ruby version to install | string | latest | +| versionManager | Version manager to install alongside Ruby: 'rbenv', 'rvm', or 'none' (ruby-build only) | string | none | ## Customizations @@ -27,7 +28,7 @@ Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies. ## OS Support -This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. +This Feature supports Linux images that ship one of the following package managers: `apt`, `dnf`/`yum`, `apk`, `zypper`, or `pacman`. The script detects the available package manager and installs the build dependencies that ruby-build needs. `bash` is required to execute the `install.sh` script. diff --git a/src/ruby/devcontainer-feature.json b/src/ruby/devcontainer-feature.json index 661c46bf0..841afbc3d 100644 --- a/src/ruby/devcontainer-feature.json +++ b/src/ruby/devcontainer-feature.json @@ -1,20 +1,26 @@ { "id": "ruby", - "version": "1.3.2", - "name": "Ruby (via rvm)", + "version": "2.0.0", + "name": "Ruby (via ruby-build)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/ruby", - "description": "Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies.", + "description": "Installs Ruby using ruby-build, with optional rbenv or rvm for version management.", "options": { "version": { "type": "string", "proposals": [ "latest", "none", - "3.4", - "3.2" + "4.0", + "3.4" ], "default": "latest", "description": "Select or enter a Ruby version to install" + }, + "versionManager": { + "type": "string", + "enum": ["none", "rbenv", "rvm"], + "default": "none", + "description": "Version manager to install alongside Ruby: 'rbenv', 'rvm', or 'none' (ruby-build only)" } }, "customizations": { @@ -25,17 +31,15 @@ "settings": { "github.copilot.chat.codeGeneration.instructions": [ { - "text": "This dev container includes Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies pre-installed and available on the `PATH`, along with the Ruby language extension for Ruby development." + "text": "This dev container installs Ruby via ruby-build. rbenv or rvm may also be available depending on the versionManager option. The default Ruby is on the PATH via /usr/local/rubies/current/bin (or rbenv shims if rbenv is the version manager)." } ] } } }, "containerEnv": { - "GEM_PATH": "/usr/local/rvm/gems/default:/usr/local/rvm/gems/default@global", - "GEM_HOME": "/usr/local/rvm/gems/default", - "MY_RUBY_HOME": "/usr/local/rvm/rubies/default", - "PATH": "/usr/local/rvm/gems/default/bin:/usr/local/rvm/gems/default@global/bin:/usr/local/rvm/rubies/default/bin:/usr/local/share/rbenv/bin:${PATH}" + "RBENV_ROOT": "/usr/local/share/rbenv", + "PATH": "/usr/local/share/rbenv/shims:/usr/local/share/rbenv/bin:/usr/local/rubies/current/bin:${PATH}" }, "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" diff --git a/src/ruby/install.sh b/src/ruby/install.sh index 39cb5be03..2b990cda8 100755 --- a/src/ruby/install.sh +++ b/src/ruby/install.sh @@ -10,24 +10,27 @@ RUBY_VERSION="${VERSION:-"latest"}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" -UPDATE_RC="${UPDATE_RC:-"true"}" INSTALL_RUBY_TOOLS="${INSTALL_RUBY_TOOLS:-"true"}" -# Comma-separated list of ruby versions to be installed (with rvm) -# alongside RUBY_VERSION, but not set as default. +# Comma-separated list of ruby versions to be installed alongside RUBY_VERSION, +# but not set as default. ADDITIONAL_VERSIONS="${ADDITIONALVERSIONS:-""}" -# Note: ruby-debug-ide will install the right version of debase if missing and -# installing debase directly fails on Ruby 3.1.0 as of 1/7/2022, so omitting. -# installing ruby-debug-ide on debian fails, so omitting. +VERSION_MANAGER="${VERSIONMANAGER:-"none"}" + DEFAULT_GEMS="rake" +RUBY_BUILD_DIR="/usr/local/share/ruby-build" +RUBIES_DIR="/usr/local/rubies" +RUBY_GROUP="ruby" +RBENV_ROOT="/usr/local/share/rbenv" +RVM_PATH="/usr/local/rvm" RVM_GPG_KEYS="409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB" set -e -# Clean up -rm -rf /var/lib/apt/lists/* +# Force apt to refresh its lists below by clearing them up front (no-op on non-apt systems). +rm -rf /var/lib/apt/lists/* 2>/dev/null || true 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 +42,6 @@ rm -f /etc/profile.d/00-restore-env.sh echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh chmod +x /etc/profile.d/00-restore-env.sh -# Determine the appropriate non-root user if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then USERNAME="" POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") @@ -56,19 +58,35 @@ elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then USERNAME=root fi -updaterc() { - if [ "${UPDATE_RC}" = "true" ]; then - echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." - if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then - echo -e "$1" >> /etc/bash.bashrc - fi - if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then - echo -e "$1" >> /etc/zsh/zshrc - fi +architecture="$(uname -m)" +if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "x86_64" ] && [ "${architecture}" != "arm64" ] && [ "${architecture}" != "aarch64" ]; then + echo "(!) Architecture $architecture unsupported" + exit 1 +fi + +clone_or_update_repo() { + local repo=$1 dest=$2 + if [ ! -d "${dest}" ]; then + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "${repo}" "${dest}" + else + git -C "${dest}" fetch --depth=1 origin && \ + git -C "${dest}" reset --hard origin/HEAD || true fi } -# Get the list of GPG key servers that are reachable +apply_group_perms() { + local dir=$1 + chgrp -R "${RUBY_GROUP}" "${dir}" 2>/dev/null || true + chmod -R g+rw "${dir}" 2>/dev/null || true + find "${dir}" -type d -exec chmod g+s {} + 2>/dev/null || true +} + get_gpg_key_servers() { declare -A keyservers_curl_map=( ["hkp://keyserver.ubuntu.com"]="http://keyserver.ubuntu.com:11371" @@ -78,15 +96,15 @@ get_gpg_key_servers() { ) local curl_args="" - local keyserver_reachable=false # Flag to indicate if any keyserver is reachable + local keyserver_reachable=false - if [ ! -z "${KEYSERVER_PROXY}" ]; then + if [ -n "${KEYSERVER_PROXY:-}" ]; then curl_args="--proxy ${KEYSERVER_PROXY}" fi for keyserver in "${!keyservers_curl_map[@]}"; do local keyserver_curl_url="${keyservers_curl_map[${keyserver}]}" - if curl -s ${curl_args} --max-time 5 ${keyserver_curl_url} > /dev/null; then + if curl -s ${curl_args} --max-time 5 "${keyserver_curl_url}" > /dev/null; then echo "keyserver ${keyserver}" keyserver_reachable=true else @@ -100,31 +118,24 @@ get_gpg_key_servers() { fi } -# Import the specified key in a variable name passed in as receive_gpg_keys() { local keys=${!1} local keyring_args="" - if [ ! -z "$2" ]; then + if [ -n "${2:-}" ]; then keyring_args="--no-default-keyring --keyring \"$2\"" fi - # Install curl - if ! type curl > /dev/null 2>&1; then - check_packages curl - fi - - # Use a temporary location for gpg keys to avoid polluting image export GNUPGHOME="/tmp/tmp-gnupg" - mkdir -p ${GNUPGHOME} - chmod 700 ${GNUPGHOME} - echo -e "disable-ipv6\n$(get_gpg_key_servers)" > ${GNUPGHOME}/dirmngr.conf - # GPG key download sometimes fails for some reason and retrying fixes it. + mkdir -p "${GNUPGHOME}" + chmod 700 "${GNUPGHOME}" + echo -e "disable-ipv6\n$(get_gpg_key_servers)" > "${GNUPGHOME}/dirmngr.conf" + local retry_count=0 local gpg_ok="false" set +e - until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; - do + until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; do echo "(*) Downloading GPG key..." + # shellcheck disable=SC2086 ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true" if [ "${gpg_ok}" != "true" ]; then echo "(*) Failed getting key, retrying in 10s..." @@ -139,306 +150,305 @@ receive_gpg_keys() { fi } -# Figure out correct version of a three part version number is not passed -find_version_from_git_tags() { - local variable_name=$1 - local requested_version=${!variable_name} - if [ "${requested_version}" = "none" ]; then return; fi - local repository=$2 - local prefix=${3:-"tags/v"} - local separator=${4:-"."} - local last_part_optional=${5:-"false"} - if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then - local escaped_separator=${separator//./\\.} - local last_part - if [ "${last_part_optional}" = "true" ]; then - last_part="(${escaped_separator}[0-9]+)?" - else - last_part="${escaped_separator}[0-9]+" - fi - local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$" - local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)" - if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then - declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" - else - set +e - declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")" - set -e - fi - fi - if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then - echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 +default_ruby_version() { + [ -L "${RUBIES_DIR}/current" ] && basename "$(readlink "${RUBIES_DIR}/current")" +} + +install_build_deps() { + if command -v apt-get > /dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -y + # libgdbm-dev pulls the appropriate libgdbm runtime, so no version-specific package is needed. + apt-get -y install --no-install-recommends \ + curl ca-certificates git autoconf bison patch build-essential \ + libssl-dev libyaml-dev libreadline-dev zlib1g-dev libgmp-dev \ + libncurses-dev libffi-dev libgdbm-dev libdb-dev uuid-dev + elif command -v dnf > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then + local pm + pm="$(command -v dnf || command -v yum)" + "${pm}" install -y \ + curl ca-certificates git gcc make patch autoconf bison \ + openssl-devel libyaml-devel zlib-devel libffi-devel \ + readline-devel ncurses-devel gdbm-devel + elif command -v apk > /dev/null 2>&1; then + apk add --no-cache \ + bash curl ca-certificates git build-base linux-headers \ + autoconf bison patch openssl-dev yaml-dev zlib-dev \ + readline-dev ncurses-dev libffi-dev gdbm-dev + elif command -v zypper > /dev/null 2>&1; then + zypper --non-interactive install --no-recommends \ + curl ca-certificates git gcc-c++ make patch \ + autoconf automake libtool bison \ + libopenssl-devel libyaml-devel zlib-devel libffi-devel \ + readline-devel ncurses-devel gdbm-devel + elif command -v pacman > /dev/null 2>&1; then + pacman -Sy --noconfirm --needed \ + curl ca-certificates git base-devel autoconf bison \ + openssl libyaml zlib libffi readline ncurses gdbm + else + echo "(!) No supported package manager found. Install Ruby build dependencies manually." exit 1 fi - echo "${variable_name}=${!variable_name}" } -# Use semver logic to decrement a version number then look for the closest match -find_prev_version_from_git_tags() { - local variable_name=$1 - local current_version=${!variable_name} - local repository=$2 - # Normally a "v" is used before the version number, but support alternate cases - local prefix=${3:-"tags/v"} - # Some repositories use "_" instead of "." for version number part separation, support that - local separator=${4:-"."} - # Some tools release versions that omit the last digit (e.g. go) - local last_part_optional=${5:-"false"} - # Some repositories may have tags that include a suffix (e.g. actions/node-versions) - local version_suffix_regex=$6 - # Try one break fix version number less if we get a failure. Use "set +e" since "set -e" can cause failures in valid scenarios. - set +e - major="$(echo "${current_version}" | grep -oE '^[0-9]+' || echo '')" - minor="$(echo "${current_version}" | grep -oP '^[0-9]+\.\K[0-9]+' || echo '')" - breakfix="$(echo "${current_version}" | grep -oP '^[0-9]+\.[0-9]+\.\K[0-9]+' 2>/dev/null || echo '')" - - if [ "${minor}" = "0" ] && [ "${breakfix}" = "0" ]; then - ((major=major-1)) - declare -g ${variable_name}="${major}" - # Look for latest version from previous major release - find_version_from_git_tags "${variable_name}" "${repository}" "${prefix}" "${separator}" "${last_part_optional}" - # Handle situations like Go's odd version pattern where "0" releases omit the last part - elif [ "${breakfix}" = "" ] || [ "${breakfix}" = "0" ]; then - ((minor=minor-1)) - declare -g ${variable_name}="${major}.${minor}" - # Look for latest version from previous minor release - find_version_from_git_tags "${variable_name}" "${repository}" "${prefix}" "${separator}" "${last_part_optional}" - else - ((breakfix=breakfix-1)) - if [ "${breakfix}" = "0" ] && [ "${last_part_optional}" = "true" ]; then - declare -g ${variable_name}="${major}.${minor}" - else - declare -g ${variable_name}="${major}.${minor}.${breakfix}" - fi - fi - set -e +install_ruby_build() { + clone_or_update_repo "https://github.com/rbenv/ruby-build.git" "${RUBY_BUILD_DIR}" + ln -sf "${RUBY_BUILD_DIR}/bin/ruby-build" /usr/local/bin/ruby-build } -apt_get_update() -{ - if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update -y +resolve_ruby_version() { + local requested=$1 + local definitions_dir="${RUBY_BUILD_DIR}/share/ruby-build" + local stable_versions + stable_versions="$(ls "${definitions_dir}" 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)" + + if [ -z "${stable_versions}" ]; then + echo "(!) ruby-build has no version definitions at ${definitions_dir}." >&2 + exit 1 fi + + case "${requested}" in + latest|current|lts) + echo "${stable_versions}" | tail -n 1 + ;; + *) + if echo "${stable_versions}" | grep -qx "${requested}"; then + echo "${requested}" + return + fi + # Resolve a partial X.Y to the highest matching X.Y.Z. + local match + match="$(echo "${stable_versions}" | grep -E "^${requested//./\\.}\\.[0-9]+$" | sort -V | tail -n 1)" + if [ -n "${match}" ]; then + echo "${match}" + return + fi + echo "(!) Ruby version '${requested}' is not known to ruby-build." >&2 + exit 1 + ;; + esac } -# Checks if packages are installed and installs them if not -check_packages() { - if ! dpkg -s "$@" > /dev/null 2>&1; then - apt_get_update - apt-get -y install --no-install-recommends "$@" +install_ruby_version() { + local requested=$1 + local set_default=$2 + local resolved + resolved="$(resolve_ruby_version "${requested}")" + local prefix="${RUBIES_DIR}/${resolved}" + + if [ -x "${prefix}/bin/ruby" ]; then + echo "(!) Ruby ${resolved} already installed at ${prefix}. Skipping..." + elif [ "${VERSION_MANAGER}" = "rbenv" ] && [ -x "${RBENV_ROOT}/bin/rbenv" ]; then + echo "Installing Ruby ${resolved} via rbenv..." + mkdir -p "${RUBIES_DIR}" + LANG="${LANG:-C.UTF-8}" RBENV_ROOT="${RBENV_ROOT}" \ + "${RBENV_ROOT}/bin/rbenv" install --skip-existing "${resolved}" + # Mirror into RUBIES_DIR so the rest of the script uses a consistent path. + ln -sfn "${RBENV_ROOT}/versions/${resolved}" "${prefix}" + elif [ "${VERSION_MANAGER}" = "rvm" ] && [ -s "${RVM_PATH}/scripts/rvm" ]; then + echo "Installing Ruby ${resolved} via rvm..." + mkdir -p "${RUBIES_DIR}" + # shellcheck disable=SC1091 + source "${RVM_PATH}/scripts/rvm" + LANG="${LANG:-C.UTF-8}" rvm install "${resolved}" + local rvm_ruby="${RVM_PATH}/rubies/ruby-${resolved}" + if [ -d "${rvm_ruby}" ]; then + ln -sfn "${rvm_ruby}" "${prefix}" + fi + else + mkdir -p "${RUBIES_DIR}" + echo "Installing Ruby ${resolved} via ruby-build..." + # Ensure a UTF-8 locale so that rdoc (and other tools bundled with Ruby) + # can process non-ASCII bytes during `make install`, even on minimal + # base images that ship with no LANG set (e.g. Debian 11 bullseye). + LANG="${LANG:-C.UTF-8}" ruby-build "${resolved}" "${prefix}" + fi + + if [ "${set_default}" = "true" ]; then + ln -sfn "${prefix}" "${RUBIES_DIR}/current" fi } -# Ensure apt is in non-interactive to avoid prompts -export DEBIAN_FRONTEND=noninteractive +# Called before ruby versions are installed so that install_ruby_version() +# can delegate to 'rbenv install'. +install_rbenv() { + echo "Installing rbenv..." + clone_or_update_repo "https://github.com/rbenv/rbenv.git" "${RBENV_ROOT}" -architecture="$(uname -m)" -if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "x86_64" ] && [ "${architecture}" != "arm64" ] && [ "${architecture}" != "aarch64" ]; then - echo "(!) Architecture $architecture unsupported" - exit 1 -fi + ln -sf "${RBENV_ROOT}/bin/rbenv" /usr/local/bin/rbenv -# Install dependencies -# Removed software-properties-common package from here as it has been removed for debian trixie(13) -check_packages curl ca-certificates build-essential gnupg2 libreadline-dev \ - procps dirmngr gawk autoconf automake bison libffi-dev libgdbm-dev libncurses5-dev \ - libsqlite3-dev libtool libyaml-dev pkg-config sqlite3 zlib1g-dev libgmp-dev libssl-dev -if ! type git > /dev/null 2>&1; then - check_packages git -fi + # Wire the already-installed ruby-build as an rbenv plugin so that + # 'rbenv install' works out of the box. + mkdir -p "${RBENV_ROOT}/plugins" + if [ ! -e "${RBENV_ROOT}/plugins/ruby-build" ]; then + ln -sfn "${RUBY_BUILD_DIR}" "${RBENV_ROOT}/plugins/ruby-build" + fi + mkdir -p "${RBENV_ROOT}/versions" + echo "rbenv ready at ${RBENV_ROOT}." +} -# Conditionally install software-properties-common (skip on Debian Trixie) -if type apt-get >/dev/null 2>&1; then - if [ -f /etc/os-release ]; then - . /etc/os-release - if [ "${ID}" = "debian" ] && [ "${VERSION_CODENAME}" = "trixie" ]; then - echo "Skipping software-properties-common on Debian Trixie." - else - check_packages software-properties-common +# Called after ruby versions are installed. +finalize_rbenv() { + # When rubies were installed via ruby-build (not 'rbenv install'), symlink them + # into rbenv's versions directory so 'rbenv versions' shows them. + # Skip entries that already point into RBENV_ROOT to avoid circular symlinks. + for ruby_dir in "${RUBIES_DIR}"/[0-9]*/; do + [ -d "${ruby_dir}" ] || continue + local ver + ver="$(basename "${ruby_dir%/}")" + local real_target + real_target="$(readlink -f "${ruby_dir%/}" 2>/dev/null || true)" + if [ "${real_target}" = "${RBENV_ROOT}/versions/${ver}" ]; then + continue fi - else - # Fallback for apt-based systems without /etc/os-release - check_packages software-properties-common + ln -sfn "${ruby_dir%/}" "${RBENV_ROOT}/versions/${ver}" + done + + # Set the rbenv global version to match the ruby-build default. + local default_ver + default_ver="$(default_ruby_version)" + if [ -n "${default_ver}" ]; then + echo "${default_ver}" > "${RBENV_ROOT}/version" fi -fi -# Function to fetch the version released prior to the latest version -get_previous_version() { - local url=$1 - local repo_url=$2 - variable_name=$3 - prev_version=${!variable_name} - - output=$(curl -s "$repo_url"); - - #install jq - check_packages jq - - message=$(echo "$output" | jq -r '.message') - - if [[ $message == "API rate limit exceeded"* ]]; then - echo -e "\nAn attempt to find latest version using GitHub Api Failed... \nReason: ${message}" - echo -e "\nAttempting to find latest version using GitHub tags." - find_prev_version_from_git_tags prev_version "$url" "tags/v" "_" - declare -g ${variable_name}="${prev_version}" - else - echo -e "\nAttempting to find latest version using GitHub Api." - version=$(echo "$output" | jq -r '.tag_name' | tr '_' '.') - declare -g ${variable_name}="${version#v}" - fi - echo "${variable_name}=${!variable_name}" -} + apply_group_perms "${RBENV_ROOT}" -get_github_api_repo_url() { - local url=$1 - echo "${url/https:\/\/github.com/https:\/\/api.github.com\/repos}/releases/latest" + # Profile script for login shells (non-login shells rely on containerEnv + # which already prepends RBENV_ROOT/shims and RBENV_ROOT/bin). + cat > /etc/profile.d/rbenv.sh << 'RBENV_PROFILE' +export RBENV_ROOT=/usr/local/share/rbenv +export PATH="${RBENV_ROOT}/bin:${RBENV_ROOT}/shims:${PATH}" +eval "$(rbenv init - --no-rehash)" 2>/dev/null || true +RBENV_PROFILE + chmod +x /etc/profile.d/rbenv.sh + + RBENV_ROOT="${RBENV_ROOT}" "${RBENV_ROOT}/bin/rbenv" rehash 2>/dev/null || true + echo "rbenv configured." } +# Called before ruby versions are installed so that install_ruby_version() +# can delegate to 'rvm install'. +install_rvm() { + echo "Installing rvm..." -# Figure out correct version of a three part version number is not passed -RUBY_URL="https://github.com/ruby/ruby" -ORIGINAL_RUBY_VERSION=$RUBY_VERSION -find_version_from_git_tags RUBY_VERSION $RUBY_URL "tags/v" "_" + receive_gpg_keys RVM_GPG_KEYS -set_rvm_install_args() { - RUBY_VERSION=$1 - if [ "${RUBY_VERSION}" = "none" ]; then - RVM_INSTALL_ARGS="" - elif [[ "$(ruby -v)" = *"${RUBY_VERSION}"* ]]; then - echo "(!) Ruby is already installed with version ${RUBY_VERSION}. Skipping..." - RVM_INSTALL_ARGS="" - else - if [ "${RUBY_VERSION}" = "latest" ] || [ "${RUBY_VERSION}" = "current" ] || [ "${RUBY_VERSION}" = "lts" ]; then - RVM_INSTALL_ARGS="--ruby" - RUBY_VERSION="" - else - RVM_INSTALL_ARGS="--ruby=${RUBY_VERSION}" - fi - if [ "${INSTALL_RUBY_TOOLS}" = "true" ]; then - SKIP_GEM_INSTALL="true" - else - DEFAULT_GEMS="" - fi + curl -sSL https://get.rvm.io | bash -s stable --path "${RVM_PATH}" + + # rvm is a shell function, so we must source it before calling 'rvm' below. + # shellcheck disable=SC1091 + if [ -s "${RVM_PATH}/scripts/rvm" ]; then + source "${RVM_PATH}/scripts/rvm" fi + echo "rvm ready at ${RVM_PATH}." } -install_previous_version() { - if [[ $ORIGINAL_RUBY_VERSION == "latest" ]]; then - repo_url=$(get_github_api_repo_url "$RUBY_URL") - get_previous_version "${RUBY_URL}" "${repo_url}" RUBY_VERSION - set_rvm_install_args $RUBY_VERSION - curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles ${RVM_INSTALL_ARGS} --with-default-gems="${DEFAULT_GEMS}" 2>&1 - else - echo "Failed to install Ruby version $ORIGINAL_RUBY_VERSION. Exiting..." +finalize_rvm() { + # shellcheck disable=SC1091 + [ -s "${RVM_PATH}/scripts/rvm" ] && source "${RVM_PATH}/scripts/rvm" || true + + # When rubies were installed via ruby-build (not 'rvm install'), mount them + # into rvm so 'rvm list' shows them. Skip entries that already live under + # RVM_PATH to avoid double-mounting. + if [ -d "${RUBIES_DIR}" ]; then + for ruby_dir in "${RUBIES_DIR}"/[0-9]*/; do + [ -d "${ruby_dir}" ] || continue + local ver + ver="$(basename "${ruby_dir%/}")" + local real_target + real_target="$(readlink -f "${ruby_dir%/}" 2>/dev/null || true)" + if [[ "${real_target}" == "${RVM_PATH}/rubies/"* ]]; then + continue + fi + rvm mount "${ruby_dir%/}" -n "${ver}" 2>/dev/null || true + done fi -} -# Just install Ruby if RVM already installed -if rvm --version > /dev/null; then - echo "Ruby Version Manager already exists." - if [[ "$(ruby -v)" = *"${RUBY_VERSION}"* ]]; then - echo "(!) Ruby is already installed with version ${RUBY_VERSION}. Skipping..." - elif [ "${RUBY_VERSION}" != "none" ]; then - echo "Installing specified Ruby version." - su ${USERNAME} -c "rvm install ruby ${RUBY_VERSION}" + # Set the rvm default to match the ruby-build default. + local default_ver + default_ver="$(default_ruby_version)" + if [ -n "${default_ver}" ]; then + local real_current + real_current="$(readlink -f "${RUBIES_DIR}/current" 2>/dev/null || true)" + if [[ "${real_current}" == "${RVM_PATH}/rubies/"* ]]; then + # Installed via 'rvm install': use the version name directly. + rvm use "${default_ver}" --default 2>/dev/null || true + else + # Installed via ruby-build and mounted: use the 'ext-' prefix. + rvm use "ext-${default_ver}" --default 2>/dev/null || true + fi fi - SKIP_GEM_INSTALL="false" - SKIP_RBENV_RBUILD="true" -else - # Install RVM - receive_gpg_keys RVM_GPG_KEYS - # Determine appropriate settings for rvm installer - set_rvm_install_args $RUBY_VERSION - # Create rvm group as a system group to reduce the odds of conflict with local user UIDs - if ! cat /etc/group | grep -e "^rvm:" > /dev/null 2>&1; then - groupadd -r rvm + + echo "source ${RVM_PATH}/scripts/rvm" > /etc/profile.d/rvm.sh + chmod +x /etc/profile.d/rvm.sh + + if [ "${USERNAME}" != "root" ] && id -u "${USERNAME}" > /dev/null 2>&1; then + usermod -aG rvm "${USERNAME}" 2>/dev/null || true fi - # Install rvm - curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles ${RVM_INSTALL_ARGS} --with-default-gems="${DEFAULT_GEMS}" 2>&1 || install_previous_version - usermod -aG rvm ${USERNAME} - source /usr/local/rvm/scripts/rvm - rvm fix-permissions system - rm -rf ${GNUPGHOME} -fi + echo "rvm configured." +} + +install_build_deps +install_ruby_build -if [ "${INSTALL_RUBY_TOOLS}" = "true" ]; then - # Non-root user may not have "gem" in path when script is run and no ruby version - # is installed by rvm, so handle this by using root's default gem in this case - ROOT_GEM="$(which gem || echo "")" - ${ROOT_GEM} install ${DEFAULT_GEMS} +# Create a shared "ruby" group so the configured user can write under the rubies tree. +if ! getent group "${RUBY_GROUP}" > /dev/null 2>&1; then + groupadd -r "${RUBY_GROUP}" 2>/dev/null || addgroup -S "${RUBY_GROUP}" 2>/dev/null || true +fi +mkdir -p "${RUBIES_DIR}" +chgrp "${RUBY_GROUP}" "${RUBIES_DIR}" 2>/dev/null || true +chmod 2775 "${RUBIES_DIR}" 2>/dev/null || true + +# Set up the version manager BEFORE installing Ruby versions so that +# install_ruby_version() can delegate to it when requested. +if [ "${VERSION_MANAGER}" = "rbenv" ]; then + install_rbenv +elif [ "${VERSION_MANAGER}" = "rvm" ]; then + install_rvm fi -# VS Code server usually first in the path, so silence annoying rvm warning (that does not apply) and then source it -updaterc "if ! grep rvm_silence_path_mismatch_check_flag \$HOME/.rvmrc > /dev/null 2>&1; then echo 'rvm_silence_path_mismatch_check_flag=1' >> \$HOME/.rvmrc; fi\nsource /usr/local/rvm/scripts/rvm > /dev/null 2>&1" +if [ "${RUBY_VERSION}" != "none" ]; then + install_ruby_version "${RUBY_VERSION}" "true" +fi -# Additional ruby versions to be installed but not be set as default. if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then OLDIFS=$IFS IFS="," read -a additional_versions <<< "$ADDITIONAL_VERSIONS" for version in "${additional_versions[@]}"; do - # Figure out correct version of a three part version number is not passed - find_version_from_git_tags version $RUBY_URL "tags/v" "_" - source /usr/local/rvm/scripts/rvm - rvm install ruby ${version} + install_ruby_version "${version}" "false" done IFS=$OLDIFS fi -# Install rbenv/ruby-build for good measure -if [ "${SKIP_RBENV_RBUILD}" != "true" ]; then +# Expose the default Ruby on the PATH for all login shells. +echo 'export PATH="/usr/local/rubies/current/bin:${PATH}"' > /etc/profile.d/ruby.sh +chmod +x /etc/profile.d/ruby.sh - if [[ ! -d "/usr/local/share/rbenv" ]]; then - git clone --depth=1 \ - -c core.eol=lf \ - -c core.autocrlf=false \ - -c fsck.zeroPaddedFilemode=ignore \ - -c fetch.fsck.zeroPaddedFilemode=ignore \ - -c receive.fsck.zeroPaddedFilemode=ignore \ - https://github.com/rbenv/rbenv.git /usr/local/share/rbenv - fi - - if [[ ! -d "/usr/local/share/ruby-build" ]]; then - git clone --depth=1 \ - -c core.eol=lf \ - -c core.autocrlf=false \ - -c fsck.zeroPaddedFilemode=ignore \ - -c fetch.fsck.zeroPaddedFilemode=ignore \ - -c receive.fsck.zeroPaddedFilemode=ignore \ - https://github.com/rbenv/ruby-build.git /usr/local/share/ruby-build - mkdir -p /root/.rbenv/plugins - - ln -s /usr/local/share/ruby-build /root/.rbenv/plugins/ruby-build - fi - - if [ "${USERNAME}" != "root" ]; then - mkdir -p /home/${USERNAME}/.rbenv/plugins - - if [[ ! -d "/home/${USERNAME}/.rbenv/plugins/ruby-build" ]]; then - ln -s /usr/local/share/ruby-build /home/${USERNAME}/.rbenv/plugins/ruby-build - fi - - # Oryx expects ruby to be installed in this specific path, else it breaks the oryx magic for ruby projects. - if [ ! -f /usr/local/rvm/gems/default/bin/ruby ]; then - ln -s /usr/local/rvm/rubies/default/bin/ruby /usr/local/rvm/gems/default/bin - fi +if [ "${RUBY_VERSION}" != "none" ] && [ "${INSTALL_RUBY_TOOLS}" = "true" ]; then + "${RUBIES_DIR}/current/bin/gem" install --no-document ${DEFAULT_GEMS} +fi - chown -R "${USERNAME}:rvm" "/home/${USERNAME}/.rbenv/" - chmod -R g+r+w "/home/${USERNAME}/.rbenv" - find "/home/${USERNAME}/.rbenv" -type d | xargs -n 1 chmod g+s +# Make sure the configured user can install gems against the shared rubies tree. +if [ "${USERNAME}" != "root" ] && id -u "${USERNAME}" > /dev/null 2>&1; then + if command -v usermod > /dev/null 2>&1; then + usermod -aG "${RUBY_GROUP}" "${USERNAME}" || true + elif command -v addgroup > /dev/null 2>&1; then + addgroup "${USERNAME}" "${RUBY_GROUP}" || true fi fi -chown -R "${USERNAME}:rvm" "/usr/local/rvm/" -chmod -R g+r+w "/usr/local/rvm/" -find "/usr/local/rvm/" -type d | xargs -n 1 chmod g+s +apply_group_perms "${RUBIES_DIR}" -# Clean up -rvm cleanup all -${ROOT_GEM} cleanup +if command -v apt-get > /dev/null 2>&1; then + rm -rf /var/lib/apt/lists/* +fi -# Clean up -rm -rf /var/lib/apt/lists/* +# Finalize the version manager now that all ruby versions are installed. +if [ "${VERSION_MANAGER}" = "rbenv" ]; then + finalize_rbenv +elif [ "${VERSION_MANAGER}" = "rvm" ]; then + finalize_rvm +fi echo "Done!" diff --git a/test/ruby/install_additional_ruby.sh b/test/ruby/install_additional_ruby.sh index 12b77def3..90c34f724 100644 --- a/test/ruby/install_additional_ruby.sh +++ b/test/ruby/install_additional_ruby.sh @@ -5,11 +5,11 @@ set -e # Optional: Import test library source dev-container-features-test-lib -check "ruby version 3.4.2 installed as default" ruby -v | grep 3.4.2 -check "ruby version 3.2.8 installed" rvm list | grep 3.2.8 -check "ruby version 3.3.2 installed" rvm list | grep 3.3.2 - -check "rbenv" bash -c 'eval "$(rbenv init -)" && rbenv --version' +check "ruby-build available" ruby-build --version +check "ruby version 3.4.2 installed as default" bash -c "ruby -v | grep 3.4.2" +check "ruby version 3.4.2 prefix present" test -x /usr/local/rubies/3.4.2/bin/ruby +check "ruby version 3.3.2 prefix present" test -x /usr/local/rubies/3.3.2/bin/ruby +check "ruby version 3.2 series installed" bash -c "ls /usr/local/rubies | grep -E '^3\\.2\\.'" check "rake" bash -c "gem list | grep rake" # Report result diff --git a/test/ruby/install_additional_ruby_trixie.sh b/test/ruby/install_additional_ruby_trixie.sh deleted file mode 100644 index 76f7c9028..000000000 --- a/test/ruby/install_additional_ruby_trixie.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e - -# Optional: Import test library -source dev-container-features-test-lib - -check "ruby version 3.4.2 installed as default" ruby -v | grep 3.4.2 -check "ruby version 3.2.8 installed" rvm list | grep 3.2.8 -check "ruby version 3.3.2 installed" rvm list | grep 3.3.2 - -check "rbenv" bash -c 'eval "$(rbenv init -)" && rbenv --version' -check "rake" bash -c "gem list | grep rake" - -# Report result -reportResults - diff --git a/test/ruby/install_additional_ruby_trixie.sh b/test/ruby/install_additional_ruby_trixie.sh new file mode 120000 index 000000000..bddda2d3a --- /dev/null +++ b/test/ruby/install_additional_ruby_trixie.sh @@ -0,0 +1 @@ +install_additional_ruby.sh \ No newline at end of file diff --git a/test/ruby/install_ruby_trixie_base.sh b/test/ruby/install_ruby_trixie_base.sh index f90c76cc4..609187ccb 100644 --- a/test/ruby/install_ruby_trixie_base.sh +++ b/test/ruby/install_ruby_trixie_base.sh @@ -5,11 +5,11 @@ set -e # Optional: Import test library source dev-container-features-test-lib -# Definition specific tests -check "ruby version" ruby --version -check "rvm" rvm --version +# The feature was invoked with version=none on a base image that already ships +# Ruby. ruby-build should still be installed so additional versions can be added. +check "ruby version" ruby --version check "gem version" gem --version +check "ruby-build available" ruby-build --version # Report result reportResults - diff --git a/test/ruby/ruby_fallback_test.sh b/test/ruby/ruby_fallback_test.sh deleted file mode 100644 index a4ec9a6b5..000000000 --- a/test/ruby/ruby_fallback_test.sh +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/bash - -set -e - -# Optional: Import test library -source dev-container-features-test-lib - -USERNAME="automatic" -echo -e "\nRVM version installed previously by ruby feature ..." -check "rvm" rvm --version -check "ruby" ruby -v - -trap 'echo "Last executed command failed at line ${LINENO}"' ERR - -RVM_GPG_KEYS="409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB" - -# Clean up -rm -rf /var/lib/apt/lists/* - -# Determine the appropriate non-root user -if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then - USERNAME="" - POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") - for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do - if id -u ${CURRENT_USER} > /dev/null 2>&1; then - USERNAME=${CURRENT_USER} - break - fi - done - if [ "${USERNAME}" = "" ]; then - USERNAME=root - fi -elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then - USERNAME=root -fi - -# Ensure apt is in non-interactive to avoid prompts -export DEBIAN_FRONTEND=noninteractive - -architecture="$(uname -m)" -if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "x86_64" ] && [ "${architecture}" != "arm64" ] && [ "${architecture}" != "aarch64" ]; then - echo "(!) Architecture $architecture unsupported" - exit 1 -fi - -apt_get_update() -{ - if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update -y - fi -} - -# Checks if packages are installed and installs them if not -check_packages() { - if ! dpkg -s "$@" > /dev/null 2>&1; then - apt_get_update - apt-get -y install --no-install-recommends "$@" - fi -} - -# Get the list of GPG key servers that are reachable -get_gpg_key_servers() { - declare -A keyservers_curl_map=( - ["hkp://keyserver.ubuntu.com"]="http://keyserver.ubuntu.com:11371" - ["hkp://keyserver.ubuntu.com:80"]="http://keyserver.ubuntu.com" - ["hkps://keys.openpgp.org"]="https://keys.openpgp.org" - ["hkp://keyserver.pgp.com"]="http://keyserver.pgp.com:11371" - ) - - local curl_args="" - local keyserver_reachable=false # Flag to indicate if any keyserver is reachable - - if [ ! -z "${KEYSERVER_PROXY}" ]; then - curl_args="--proxy ${KEYSERVER_PROXY}" - fi - - for keyserver in "${!keyservers_curl_map[@]}"; do - local keyserver_curl_url="${keyservers_curl_map[${keyserver}]}" - if curl -s ${curl_args} --max-time 5 ${keyserver_curl_url} > /dev/null; then - echo "keyserver ${keyserver}" - keyserver_reachable=true - else - echo "(*) Keyserver ${keyserver} is not reachable." >&2 - fi - done - - if ! $keyserver_reachable; then - echo "(!) No keyserver is reachable." >&2 - exit 1 - fi -} - -# Import the specified key in a variable name passed in as -receive_gpg_keys() { - local keys=${!1} - local keyring_args="" - if [ ! -z "$2" ]; then - keyring_args="--no-default-keyring --keyring \"$2\"" - fi - - # Install curl - if ! type curl > /dev/null 2>&1; then - check_packages curl - fi - - # Use a temporary location for gpg keys to avoid polluting image - export GNUPGHOME="/tmp/tmp-gnupg" - mkdir -p ${GNUPGHOME} - chmod 700 ${GNUPGHOME} - echo -e "disable-ipv6\n$(get_gpg_key_servers)" | tee ${GNUPGHOME}/dirmngr.conf > /dev/null - # GPG key download sometimes fails for some reason and retrying fixes it. - local retry_count=0 - local gpg_ok="false" - set +e - until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; - do - echo "(*) Downloading GPG key..." - ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true" - if [ "${gpg_ok}" != "true" ]; then - echo "(*) Failed getting key, retrying in 10s..." - (( retry_count++ )) - sleep 10s - fi - done - set -e - if [ "${gpg_ok}" = "false" ]; then - echo "(!) Failed to get gpg key." - exit 1 - fi -} - -# Figure out correct version of a three part version number is not passed -find_version_from_git_tags() { - local variable_name=$1 - local requested_version=${!variable_name} - if [ "${requested_version}" = "none" ]; then return; fi - local repository=$2 - local prefix=${3:-"tags/v"} - local separator=${4:-"."} - local last_part_optional=${5:-"false"} - if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then - local escaped_separator=${separator//./\\.} - local last_part - if [ "${last_part_optional}" = "true" ]; then - last_part="(${escaped_separator}[0-9]+)?" - else - last_part="${escaped_separator}[0-9]+" - fi - local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$" - local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)" - if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then - declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" - else - set +e - declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")" - set -e - fi - fi - if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then - echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 - exit 1 - fi - echo "${variable_name}=${!variable_name}" -} - -# Use semver logic to decrement a version number then look for the closest match -find_prev_version_from_git_tags() { - local variable_name=$1 - local current_version=${!variable_name} - local repository=$2 - # Normally a "v" is used before the version number, but support alternate cases - local prefix=${3:-"tags/v"} - # Some repositories use "_" instead of "." for version number part separation, support that - local separator=${4:-"."} - # Some tools release versions that omit the last digit (e.g. go) - local last_part_optional=${5:-"false"} - # Some repositories may have tags that include a suffix (e.g. actions/node-versions) - local version_suffix_regex=$6 - # Try one break fix version number less if we get a failure. Use "set +e" since "set -e" can cause failures in valid scenarios. - set +e - major="$(echo "${current_version}" | grep -oE '^[0-9]+' || echo '')" - minor="$(echo "${current_version}" | grep -oP '^[0-9]+\.\K[0-9]+' || echo '')" - breakfix="$(echo "${current_version}" | grep -oP '^[0-9]+\.[0-9]+\.\K[0-9]+' 2>/dev/null || echo '')" - - if [ "${minor}" = "0" ] && [ "${breakfix}" = "0" ]; then - ((major=major-1)) - declare -g ${variable_name}="${major}" - # Look for latest version from previous major release - find_version_from_git_tags "${variable_name}" "${repository}" "${prefix}" "${separator}" "${last_part_optional}" - # Handle situations like Go's odd version pattern where "0" releases omit the last part - elif [ "${breakfix}" = "" ] || [ "${breakfix}" = "0" ]; then - ((minor=minor-1)) - declare -g ${variable_name}="${major}.${minor}" - # Look for latest version from previous minor release - find_version_from_git_tags "${variable_name}" "${repository}" "${prefix}" "${separator}" "${last_part_optional}" - else - ((breakfix=breakfix-1)) - if [ "${breakfix}" = "0" ] && [ "${last_part_optional}" = "true" ]; then - declare -g ${variable_name}="${major}.${minor}" - else - declare -g ${variable_name}="${major}.${minor}.${breakfix}" - fi - fi - set -e -} - -# Function to fetch the version released prior to the latest version -get_previous_version() { - local url=$1 - local repo_url=$2 - local variable_name=$3 - local mode=$4 - prev_version=${!variable_name} - - output=$(curl -s "$repo_url"); - - #install jq - check_packages jq - - message=$(echo "$output" | jq -r '.message') - - if [[ $mode == "mode1" ]]; then - message="API rate limit exceeded" - else - message="" - fi - - if [[ $message == "API rate limit exceeded"* ]]; then - echo -e "\nAn attempt to find latest version using GitHub Api Failed... \nReason: ${message}" - echo -e "\nAttempting to find latest version using GitHub tags." - find_prev_version_from_git_tags prev_version "$url" "tags/v" "_" - declare -g ${variable_name}="${prev_version}" - else - echo -e "\nAttempting to find latest version using GitHub Api." - version=$(echo "$output" | jq -r '.tag_name' | tr '_' '.') - declare -g ${variable_name}="${version#v}" - fi - echo "${variable_name}=${!variable_name}" -} - -get_github_api_repo_url() { - local url=$1 - echo "${url/https:\/\/github.com/https:\/\/api.github.com\/repos}/releases/latest" -} - - -# Figure out correct version of a three part version number is not passed -ruby_url="https://github.com/ruby/ruby" - -RUBY_VERSION="3.4.xyz" - -set_rvm_install_args() { - RUBY_VERSION=$1 - if [ "${RUBY_VERSION}" = "none" ]; then - RVM_INSTALL_ARGS="" - elif [[ "$(ruby -v)" = *"${RUBY_VERSION}"* ]]; then - echo "(!) Ruby is already installed with version ${RUBY_VERSION}. Skipping..." - RVM_INSTALL_ARGS="" - else - if [ "${RUBY_VERSION}" = "latest" ] || [ "${RUBY_VERSION}" = "current" ] || [ "${RUBY_VERSION}" = "lts" ]; then - RVM_INSTALL_ARGS="--ruby" - RUBY_VERSION="" - else - RVM_INSTALL_ARGS="--ruby=${RUBY_VERSION}" - fi - if [ "${INSTALL_RUBY_TOOLS}" = "true" ]; then - SKIP_GEM_INSTALL="true" - else - DEFAULT_GEMS="" - fi - fi -} - -install_previous_version() { - mode=$1 - repo_url=$(get_github_api_repo_url "$ruby_url") - get_previous_version "${ruby_url}" "${repo_url}" RUBY_VERSION $mode - set_rvm_install_args $RUBY_VERSION - curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles ${RVM_INSTALL_ARGS} --with-default-gems="${DEFAULT_GEMS}" 2>&1 -} - -install_rvm() { - mode=$1 - # Install RVM - receive_gpg_keys RVM_GPG_KEYS - # Determine appropriate settings for rvm installer - set_rvm_install_args $RUBY_VERSION - # Create rvm group as a system group to reduce the odds of conflict with local user UIDs - if ! cat /etc/group | grep -e "^rvm:" > /dev/null 2>&1; then - groupadd -r rvm - fi - # Install rvm - curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles ${RVM_INSTALL_ARGS} --with-default-gems="${DEFAULT_GEMS}" 2>&1 || install_previous_version "$mode" - sudo usermod -aG rvm ${USERNAME} - source /usr/local/rvm/scripts/rvm - rvm fix-permissions system - rm -rf ${GNUPGHOME} -} - -install_rvm "mode1" -echo -e "\n๐Ÿ‘‰๐Ÿป๐Ÿ‘‰๐ŸปRVM version installed by test file ... (mode: 1 - install using find_prev_version_from_git_tags):" -check "rvm" rvm --version - -install_rvm "mode2" -echo -e "\n๐Ÿ‘‰๐Ÿป๐Ÿ‘‰๐ŸปRVM version installed by test file ... (mode: 1 - install using GitHub Api):" -check "rvm" rvm --version - -# Report result -reportResults \ No newline at end of file diff --git a/test/ruby/ruby_rbenv.sh b/test/ruby/ruby_rbenv.sh new file mode 100755 index 000000000..685dc6828 --- /dev/null +++ b/test/ruby/ruby_rbenv.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# rbenv (and its shims/bin dirs) is placed on PATH via containerEnv, +# so these commands should work in non-login shells too. +check "ruby version 3.4.2 active" bash -c "ruby -v | grep 3.4.2" +check "rbenv available" rbenv --version +check "rbenv lists ruby 3.4.2" bash -c "rbenv versions | grep 3.4.2" +check "rbenv global is 3.4.2" bash -c "rbenv global | grep 3.4.2" +check "rbenv shim for ruby" test -x /usr/local/share/rbenv/shims/ruby +check "ruby-build wired as rbenv plugin" test -d /usr/local/share/rbenv/plugins/ruby-build +check "rake gem installed" bash -c "gem list | grep rake" + +# Report result +reportResults diff --git a/test/ruby/ruby_rvm.sh b/test/ruby/ruby_rvm.sh new file mode 100755 index 000000000..6cea7901a --- /dev/null +++ b/test/ruby/ruby_rvm.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Ruby installed via rvm lives under /usr/local/rvm/rubies and is also +# exposed via the /usr/local/rubies/current PATH entry from containerEnv. +check "ruby version 3.4.2 active" bash -c "ruby -v | grep 3.4.2" +check "rvm binary available" /usr/local/rvm/bin/rvm --version +check "rvm ruby 3.4.2 directory exists" test -d /usr/local/rvm/rubies/ruby-3.4.2 +# rvm is implemented as a shell function, so source it before calling. +check "rvm default points to 3.4.2" bash -c "source /usr/local/rvm/scripts/rvm && rvm current | grep 3.4.2" +check "rvm profile.d hook installed" test -x /etc/profile.d/rvm.sh +check "rake gem installed" bash -c "gem list | grep rake" + +# Report result +reportResults diff --git a/test/ruby/scenarios.json b/test/ruby/scenarios.json index 7aa7c5f8e..4c3b2600c 100644 --- a/test/ruby/scenarios.json +++ b/test/ruby/scenarios.json @@ -1,8 +1,8 @@ -{ +{ "install_ruby_trixie_base": { "build": { "dockerfile": "Dockerfile" - }, + }, "features": { "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": "true", @@ -28,7 +28,7 @@ "additionalVersions": "3.2,3.3.2" } } - }, + }, "install_additional_ruby": { "image": "ubuntu:noble", "features": { @@ -39,17 +39,27 @@ } }, "ruby_debian": { - "image": "mcr.microsoft.com/devcontainers/base:bullseye", + "image": "mcr.microsoft.com/devcontainers/base:bookworm", "features": { "ruby": {} } }, - "ruby_fallback_test": { - "image": "mcr.microsoft.com/devcontainers/base:bullseye", + "ruby_rbenv": { + "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { "ruby": { - "version": "latest" + "version": "3.4.2", + "versionManager": "rbenv" + } + } + }, + "ruby_rvm": { + "image": "mcr.microsoft.com/devcontainers/base:bookworm", + "features": { + "ruby": { + "version": "3.4.2", + "versionManager": "rvm" } } } -} \ No newline at end of file +} From e21f5c8de89f9b0c191e39084a90d7dbb25ca8d0 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Thu, 25 Jun 2026 12:59:47 +0530 Subject: [PATCH 64/78] [docker-in-docker] - Move the iptables switching logic in the docker-init script and isolated tests for specific cases (#1666) * Check the tests * check the log * Adding debug statements * Another change * Check in docker-init.sh * Change test order * Check the presence of the kernel module * change the test execution order * Changes in workflows * Change the test * Further isolation * Adding a flag to switch the logic for better management. * Modify stress tests * Add docker-compose latest version * Changing to minor version upgrade as a switch flag is present * Revert "Add docker-compose latest version" This reverts commit 7360873729d711c85bc3932b8a854ffd0f6a9e91. * Reapply "Add docker-compose latest version" This reverts commit dfa45209737192e4f4a5ca8e58222c8ce47c823b. * Removing docker-compose latest version change from this PR. * Implementing review comments. * Implementing review comments * Implementing review comments further * Setting iptablesSwitchAtRuntime:true and major version bump. --- ...r-in-docker-daemon-startup-bulk-test.yaml} | 6 +- .github/workflows/test-pr-arm64.yaml | 9 +++ .github/workflows/test-pr.yaml | 37 ++++++++++++ src/docker-in-docker/README.md | 3 +- .../devcontainer-feature.json | 7 ++- src/docker-in-docker/install.sh | 35 ++++++++++- .../docker_iptables_switch_at_install.sh | 22 +++++++ .../docker_iptables_switch_at_runtime.sh | 24 ++++++++ .../docker_with_default_iptables.sh | 33 +++++++++++ .../docker_with_default_iptables_ubuntu.sh | 1 + .../docker_with_legacy_iptables.sh | 20 +++++++ .../docker_with_legacy_iptables_ubuntu.sh | 1 + test/docker-in-docker/scenarios.json | 59 ++++++++++++++++++- 13 files changed, 248 insertions(+), 9 deletions(-) rename .github/workflows/{docker-in-docker-stress-test.yaml => docker-in-docker-daemon-startup-bulk-test.yaml} (82%) create mode 100644 test/docker-in-docker/docker_iptables_switch_at_install.sh create mode 100644 test/docker-in-docker/docker_iptables_switch_at_runtime.sh create mode 100644 test/docker-in-docker/docker_with_default_iptables.sh create mode 120000 test/docker-in-docker/docker_with_default_iptables_ubuntu.sh create mode 100644 test/docker-in-docker/docker_with_legacy_iptables.sh create mode 120000 test/docker-in-docker/docker_with_legacy_iptables_ubuntu.sh diff --git a/.github/workflows/docker-in-docker-stress-test.yaml b/.github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml similarity index 82% rename from .github/workflows/docker-in-docker-stress-test.yaml rename to .github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml index a63225a13..b7ec4339e 100644 --- a/.github/workflows/docker-in-docker-stress-test.yaml +++ b/.github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml @@ -1,4 +1,4 @@ -name: "Stress test - Docker in Docker" +name: "Test Docker daemon startup in bulk - Docker in Docker" on: pull_request: paths: @@ -18,8 +18,8 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli - - name: "Generating tests for 'docker-in-docker' which validates if docker daemon is running" - run: devcontainer features test --skip-scenarios -f docker-in-docker -i mcr.microsoft.com/devcontainers/base:noble . + - name: "Generating tests for 'docker-in-docker' which validates if docker daemon is running (with iptablesSwitchAtRuntime=true)" + run: devcontainer features test -f docker-in-docker --skip-autogenerated --filter "docker_iptables_switch_at_runtime" . test-onCreate: strategy: diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index e5855ced2..ddc761000 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -75,5 +75,14 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli + - name: "Exclude iptables-isolation scenarios from docker-in-docker" + if: matrix.features == 'docker-in-docker' + run: | + sudo apt-get update && sudo apt-get install -y jq + sed 's://.*$::' test/docker-in-docker/scenarios.json \ + | jq 'del(.docker_with_default_iptables, .docker_with_default_iptables_ubuntu)' \ + > test/docker-in-docker/scenarios.json.tmp + mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json + - name: "Testing '${{ matrix.features }}' scenarios" run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index e00c50876..8b2520752 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -92,5 +92,42 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli + - name: "Exclude iptables-isolation scenarios from docker-in-docker (run in separate 'iptables-isolation' job)" + if: matrix.features == 'docker-in-docker' + run: | + sudo apt-get update && sudo apt-get install -y jq + sed 's://.*$::' test/docker-in-docker/scenarios.json \ + | jq 'del(.docker_with_default_iptables, .docker_with_default_iptables_ubuntu)' \ + > test/docker-in-docker/scenarios.json.tmp + mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json + - name: "Testing '${{ matrix.features }}' scenarios" run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . + + iptables-isolation: + needs: [detect-changes] + if: contains(fromJSON(needs.detect-changes.outputs.features), 'docker-in-docker') + runs-on: ubuntu-latest + continue-on-error: true + strategy: + fail-fast: false + matrix: + scenario: + - docker_with_default_iptables + - docker_with_default_iptables_ubuntu + steps: + - uses: actions/checkout@v6 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Isolate scenario '${{ matrix.scenario }}'" + run: | + sudo apt-get update && sudo apt-get install -y jq + sed 's://.*$::' test/docker-in-docker/scenarios.json \ + | jq '{ "${{ matrix.scenario }}": .["${{ matrix.scenario }}"] }' \ + > test/docker-in-docker/scenarios.json.tmp + mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json + + - name: "Testing docker-in-docker scenario '${{ matrix.scenario }}'" + run: devcontainer features test --features docker-in-docker --filter ${{ matrix.scenario }} --skip-autogenerated . diff --git a/src/docker-in-docker/README.md b/src/docker-in-docker/README.md index f94c9be51..6bba801a8 100644 --- a/src/docker-in-docker/README.md +++ b/src/docker-in-docker/README.md @@ -7,7 +7,7 @@ Create child containers *inside* a container, independent from the host's docker ```json "features": { - "ghcr.io/devcontainers/features/docker-in-docker:3": {} + "ghcr.io/devcontainers/features/docker-in-docker:4": {} } ``` @@ -24,6 +24,7 @@ Create child containers *inside* a container, independent from the host's docker | installDockerBuildx | Install Docker Buildx | boolean | true | | installDockerComposeSwitch | Install Compose Switch (provided docker compose is available) which is a replacement to the Compose V1 docker-compose (python) executable. It translates the command line into Compose V2 docker compose then runs the latter. | boolean | false | | disableIp6tables | Disable ip6tables (this option is only applicable for Docker versions 27 and greater) | boolean | false | +| iptablesSwitchAtRuntime | If true, the iptables alternative is selected at container start (inside docker-init.sh) instead of at image build time. Useful when the desired iptables backend depends on the host kernel at runtime rather than at build time. | boolean | true | ## Customizations diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 6d7c0431a..2710a2e32 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": "3.1.0", + "version": "4.0.0", "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.", @@ -61,6 +61,11 @@ "type": "boolean", "default": false, "description": "Disable ip6tables (this option is only applicable for Docker versions 27 and greater)" + }, + "iptablesSwitchAtRuntime": { + "type": "boolean", + "default": true, + "description": "If true, the iptables alternative is selected at container start (inside docker-init.sh) instead of at image build time. Useful when the desired iptables backend depends on the host kernel at runtime rather than at build time." } }, "entrypoint": "/usr/local/share/docker-init.sh", diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index 70a187e28..dbef9ae32 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -22,6 +22,7 @@ MICROSOFT_GPG_KEYS_ROLLING_URI="https://packages.microsoft.com/keys/microsoft-ro DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal jammy noble" DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="trixie bookworm buster bullseye bionic focal hirsute impish jammy noble resolute" DISABLE_IP6_TABLES="${DISABLEIP6TABLES:-false}" +IPTABLES_SWITCH_AT_RUNTIME="${IPTABLESSWITCHATRUNTIME:-true}" # Default: Exit on any failure. set -e @@ -313,8 +314,10 @@ if [ "${ADJUSTED_ID}" = "debian" ] && command -v update-ca-certificates > /dev/n update-ca-certificates fi -# Swap to legacy iptables for compatibility (Debian only) -if [ "${ADJUSTED_ID}" = "debian" ]; then +# Swap to legacy iptables for compatibility (Debian only) - install-time path. +# When IPTABLES_SWITCH_AT_RUNTIME=true the same logic is emitted into +# docker-init.sh and runs at container start instead. +if [ "${IPTABLES_SWITCH_AT_RUNTIME}" != "true" ] && [ "${ADJUSTED_ID}" = "debian" ]; then # On distros where legacy iptables is no longer kernel-supported (e.g. Ubuntu 26.04 / resolute), # prefer iptables-nft. Otherwise prefer legacy for backward compatibility. use_nft=false @@ -323,12 +326,15 @@ if [ "${ADJUSTED_ID}" = "debian" ]; then esac if [ "${use_nft}" = "true" ] && type iptables-nft > /dev/null 2>&1; then + echo "(*) Setting iptables alternatives to nft for better compatibility with newer kernels" update-alternatives --set iptables /usr/sbin/iptables-nft || true update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true - elif type iptables-legacy > /dev/null 2>&1; then + elif type iptables-legacy > /dev/null 2>&1 && iptables-legacy -L > /dev/null 2>&1; then + echo "(*) Setting iptables alternatives to legacy for better compatibility with Docker and older kernels" update-alternatives --set iptables /usr/sbin/iptables-legacy || true update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true elif type iptables-nft > /dev/null 2>&1; then + echo "(*) Setting iptables alternatives to nft for better compatibility with newer kernels for non resolute" update-alternatives --set iptables /usr/sbin/iptables-nft || true update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true fi @@ -970,6 +976,29 @@ DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES} EOF +# On Debian-based images, re-assert the iptables alternative at container start +# (only when the user opted into runtime switching via iptablesSwitchAtRuntime=true). +if [ "${IPTABLES_SWITCH_AT_RUNTIME}" = "true" ] && [ "${ADJUSTED_ID}" = "debian" ]; then + tee -a /usr/local/share/docker-init.sh > /dev/null \ +<< 'EOF' +# Prefer legacy only when the ip_tables kernel module is actually present. +# (Do NOT call `iptables-legacy -L/-nL` to test this โ€” it auto-modprobes ip_tables +# and would defeat hosts/scenarios where the module is intentionally absent +# such as the newer kernels which leaves out ip_tables legacy.) +if type iptables-legacy > /dev/null 2>&1 \ + && { grep -qE '^(ip_tables)\b' /proc/modules \ + || [ -d /sys/module/ip_tables ]; } \ + && update-alternatives --list iptables 2>/dev/null | grep -q '/usr/sbin/iptables-legacy'; then + update-alternatives --set iptables /usr/sbin/iptables-legacy || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true +elif type iptables-nft > /dev/null 2>&1 \ + && update-alternatives --list iptables 2>/dev/null | grep -q '/usr/sbin/iptables-nft'; then + update-alternatives --set iptables /usr/sbin/iptables-nft || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true +fi +EOF +fi + tee -a /usr/local/share/docker-init.sh > /dev/null \ << 'EOF' dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES} $(cat << 'INNEREOF' diff --git a/test/docker-in-docker/docker_iptables_switch_at_install.sh b/test/docker-in-docker/docker_iptables_switch_at_install.sh new file mode 100644 index 000000000..c87650a4c --- /dev/null +++ b/test/docker-in-docker/docker_iptables_switch_at_install.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Default behavior (iptablesSwitchAtRuntime omitted -> false): switching happens +# at image build time, so docker-init.sh should NOT contain the runtime block. +check "init-script-exists" bash -c "test -f /usr/local/share/docker-init.sh" +check "no-runtime-iptables-block" bash -c "! grep -q 'update-alternatives --set iptables' /usr/local/share/docker-init.sh" + +# The build-time switch should have set /etc/alternatives/iptables to one of the +# known backends. With the ip_tables module loaded on the host, legacy is preferred. +check "iptables-alternative-set" bash -c "readlink /etc/alternatives/iptables | grep -E 'iptables-(legacy|nft)$'" +check "iptables works" sudo iptables -L + +check "version" docker --version +check "docker-ps" bash -c "docker ps" + +# Report result +reportResults diff --git a/test/docker-in-docker/docker_iptables_switch_at_runtime.sh b/test/docker-in-docker/docker_iptables_switch_at_runtime.sh new file mode 100644 index 000000000..152a4ec98 --- /dev/null +++ b/test/docker-in-docker/docker_iptables_switch_at_runtime.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# iptablesSwitchAtRuntime=true: switching is deferred to container start, so the +# runtime block MUST have been written into docker-init.sh by install.sh. +check "init-script-exists" bash -c "test -f /usr/local/share/docker-init.sh" +check "runtime-iptables-block-present" bash -c "grep -q 'update-alternatives --set iptables' /usr/local/share/docker-init.sh" +check "runtime-iptables-block-has-legacy-branch" bash -c "grep -q '/usr/sbin/iptables-legacy' /usr/local/share/docker-init.sh" +check "runtime-iptables-block-has-nft-branch" bash -c "grep -q '/usr/sbin/iptables-nft' /usr/local/share/docker-init.sh" + +# The runtime block runs as part of docker-init.sh (the feature's entrypoint), +# so by the time these tests execute the alternative must already be set. +check "iptables-alternative-set" bash -c "readlink /etc/alternatives/iptables | grep -E 'iptables-(legacy|nft)$'" +check "iptables works" sudo iptables -L + +check "version" docker --version +check "docker-ps" bash -c "docker ps" + +# Report result +reportResults diff --git a/test/docker-in-docker/docker_with_default_iptables.sh b/test/docker-in-docker/docker_with_default_iptables.sh new file mode 100644 index 000000000..8336cda4c --- /dev/null +++ b/test/docker-in-docker/docker_with_default_iptables.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "docker-ps" bash -c "docker ps" +# Fail loudly if dockerd never finished initializing, printing the real error +check "dockerd-started-successfully" bash -c ' + if ! grep -q "Daemon has completed initialization" /tmp/dockerd.log; then + echo "โŒ Docker daemon failed to start. Last errors from /tmp/dockerd.log:" + echo "----- dockerd.log (tail) -----" + tail -n 100 /tmp/dockerd.log + echo "----- error/fatal lines -----" + grep -iE "error|fatal|failed|panic" /tmp/dockerd.log || true + exit 1 + fi +' + +check "iptables works" sudo iptables -L +check "iptables uses nf_tables" bash -c "iptables --version | grep nf_tables" + +check "version" docker --version +check "docker-ps" bash -c "docker ps" +check "log-exists" bash -c "ls /tmp/dockerd.log" +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" +check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'" + +# Report result +reportResults + diff --git a/test/docker-in-docker/docker_with_default_iptables_ubuntu.sh b/test/docker-in-docker/docker_with_default_iptables_ubuntu.sh new file mode 120000 index 000000000..7eb9de2c6 --- /dev/null +++ b/test/docker-in-docker/docker_with_default_iptables_ubuntu.sh @@ -0,0 +1 @@ +docker_with_default_iptables.sh \ No newline at end of file diff --git a/test/docker-in-docker/docker_with_legacy_iptables.sh b/test/docker-in-docker/docker_with_legacy_iptables.sh new file mode 100644 index 000000000..e29e10146 --- /dev/null +++ b/test/docker-in-docker/docker_with_legacy_iptables.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "iptables works" sudo iptables -L +check "iptables uses legacy" bash -c "iptables --version | grep legacy" + +check "version" docker --version +check "docker-ps" bash -c "docker ps" +check "log-exists" bash -c "ls /tmp/dockerd.log" +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" +check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'" + +# Report result +reportResults + diff --git a/test/docker-in-docker/docker_with_legacy_iptables_ubuntu.sh b/test/docker-in-docker/docker_with_legacy_iptables_ubuntu.sh new file mode 120000 index 000000000..5b62242e3 --- /dev/null +++ b/test/docker-in-docker/docker_with_legacy_iptables_ubuntu.sh @@ -0,0 +1 @@ +docker_with_legacy_iptables.sh \ No newline at end of file diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index a93495b51..a4ee2a3b0 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -1,4 +1,61 @@ { + "docker_iptables_switch_at_install": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": { + "moby": "false", + "iptablesSwitchAtRuntime": false + } + }, + "initializeCommand": "sudo modprobe ip_tables" + }, + // DO NOT REMOVE: This scenario is used by the docker-in-docker-daemon-startup-bulk-test workflow + "docker_iptables_switch_at_runtime": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": { + "moby": "false", + "iptablesSwitchAtRuntime": true + } + }, + "initializeCommand": "sudo modprobe ip_tables" + }, + "docker_with_default_iptables": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe --remove --remove-holders --wait 1000 ip_tables" + }, + "docker_with_legacy_iptables": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe ip_tables" + }, + "docker_with_default_iptables_ubuntu": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe --remove --remove-holders --wait 1000 ip_tables" + }, + "docker_with_legacy_iptables_ubuntu": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe ip_tables" + }, "overlayfs_containerd_root": { "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { @@ -215,7 +272,7 @@ } } }, - // DO NOT REMOVE: This scenario is used by the docker-in-docker-stress-test workflow + // DO NOT REMOVE: This scenario is used by the docker-in-docker-daemon-startup-bulk-test workflow "docker_with_on_create_command": { "image": "mcr.microsoft.com/devcontainers/base:debian", "features": { From 0eb175018a2ffb84174a2cb08be617c20553aaaf Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 30 Jun 2026 16:21:40 +0530 Subject: [PATCH 65/78] [git] - Fixing installation from source with new version `2.55.0` (#1678) --- src/git/devcontainer-feature.json | 2 +- src/git/install.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/git/devcontainer-feature.json b/src/git/devcontainer-feature.json index 5aa83a8b1..93ea7b061 100644 --- a/src/git/devcontainer-feature.json +++ b/src/git/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "git", - "version": "1.3.5", + "version": "1.3.6", "name": "Git (from source)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/git", "description": "Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.", diff --git a/src/git/install.sh b/src/git/install.sh index 7ee301b7e..ed20a8ce5 100755 --- a/src/git/install.sh +++ b/src/git/install.sh @@ -318,6 +318,7 @@ cd /tmp/git-${GIT_VERSION} git_options=("prefix=/usr/local") git_options+=("sysconfdir=/etc") git_options+=("USE_LIBPCRE=YesPlease") +git_options+=("NO_RUST=YesPlease") if [ "${ADJUSTED_ID}" = "alpine" ]; then # ref. git_options+=("NO_REGEX=YesPlease") From 1863143f13d84971a56e850929ba0019e54e0ca5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:00:41 +0100 Subject: [PATCH 66/78] Bump actions/checkout from 6 to 7 (#1674) Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../docker-in-docker-daemon-startup-bulk-test.yaml | 4 ++-- .github/workflows/linter-automated.yaml | 2 +- .github/workflows/linter-manual.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/test-all.yaml | 6 +++--- .github/workflows/test-manual.yaml | 2 +- .github/workflows/test-pr-arm64.yaml | 4 ++-- .github/workflows/test-pr.yaml | 6 +++--- .github/workflows/update-aws-cli-completer-scripts.yml | 2 +- .github/workflows/update-documentation.yml | 2 +- .github/workflows/update-dotnet-install-script.yml | 2 +- .github/workflows/validate-metadata-files.yml | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml b/.github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml index b7ec4339e..668879730 100644 --- a/.github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml +++ b/.github/workflows/docker-in-docker-daemon-startup-bulk-test.yaml @@ -13,7 +13,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -28,7 +28,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/linter-automated.yaml b/.github/workflows/linter-automated.yaml index 234f7e726..db57db29d 100644 --- a/.github/workflows/linter-automated.yaml +++ b/.github/workflows/linter-automated.yaml @@ -9,7 +9,7 @@ jobs: shellchecker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Shell Linter uses: azohra/shell-linter@v0.8.0 diff --git a/.github/workflows/linter-manual.yaml b/.github/workflows/linter-manual.yaml index 5d4081f5a..ba72f8eaf 100644 --- a/.github/workflows/linter-manual.yaml +++ b/.github/workflows/linter-manual.yaml @@ -15,7 +15,7 @@ jobs: shellchecker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Shell Linter uses: azohra/shell-linter@v0.8.0 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ab3c0a34b..96c95398b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ jobs: packages: write contents: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Publish" uses: devcontainers/action@v1 diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index 2c1405d9d..dd1d216a0 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -51,7 +51,7 @@ jobs: "mcr.microsoft.com/devcontainers/base:noble" ] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -95,7 +95,7 @@ jobs: "nix", ] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -107,7 +107,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/test-manual.yaml b/.github/workflows/test-manual.yaml index 1373f5215..18dffd1b9 100644 --- a/.github/workflows/test-manual.yaml +++ b/.github/workflows/test-manual.yaml @@ -19,7 +19,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index ddc761000..0de1f066b 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -51,7 +51,7 @@ jobs: - features: docker-in-docker baseImage: mcr.microsoft.com/devcontainers/base:ubuntu steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Load erofs module and verify" run: sudo modprobe erofs && grep erofs /proc/filesystems @@ -70,7 +70,7 @@ jobs: matrix: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 8b2520752..e18291f09 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -71,7 +71,7 @@ jobs: - features: docker-outside-of-docker baseImage: mcr.microsoft.com/devcontainers/base:ubuntu steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -87,7 +87,7 @@ jobs: matrix: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli @@ -116,7 +116,7 @@ jobs: - docker_with_default_iptables - docker_with_default_iptables_ubuntu steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli diff --git a/.github/workflows/update-aws-cli-completer-scripts.yml b/.github/workflows/update-aws-cli-completer-scripts.yml index d0c119d09..fde3a29fc 100644 --- a/.github/workflows/update-aws-cli-completer-scripts.yml +++ b/.github/workflows/update-aws-cli-completer-scripts.yml @@ -12,7 +12,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Run fetch-latest-completer-scripts.sh run: src/aws-cli/scripts/fetch-latest-completer-scripts.sh diff --git a/.github/workflows/update-documentation.yml b/.github/workflows/update-documentation.yml index 96c12176b..50a643fd7 100644 --- a/.github/workflows/update-documentation.yml +++ b/.github/workflows/update-documentation.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write if: "github.ref == 'refs/heads/main'" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Generate Documentation uses: devcontainers/action@v1 diff --git a/.github/workflows/update-dotnet-install-script.yml b/.github/workflows/update-dotnet-install-script.yml index 19aab95e2..16f737ff2 100644 --- a/.github/workflows/update-dotnet-install-script.yml +++ b/.github/workflows/update-dotnet-install-script.yml @@ -12,7 +12,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Run fetch-latest-dotnet-install.sh run: src/dotnet/scripts/fetch-latest-dotnet-install.sh diff --git a/.github/workflows/validate-metadata-files.yml b/.github/workflows/validate-metadata-files.yml index dfb5b25f0..5b24dfbee 100644 --- a/.github/workflows/validate-metadata-files.yml +++ b/.github/workflows/validate-metadata-files.yml @@ -7,7 +7,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: "Validate devcontainer-feature.json files" uses: devcontainers/action@v1 From 5e6a85458a579e737897494b313322d7c2d2ddca Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 30 Jun 2026 22:01:17 +0530 Subject: [PATCH 67/78] [oryx] - Fixing build issue (#1680) --- src/oryx/devcontainer-feature.json | 2 +- src/oryx/install.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oryx/devcontainer-feature.json b/src/oryx/devcontainer-feature.json index 9468222e8..5e60e3218 100644 --- a/src/oryx/devcontainer-feature.json +++ b/src/oryx/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "oryx", - "version": "2.0.0", + "version": "2.0.1", "name": "Oryx", "description": "Installs the oryx CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/oryx", diff --git a/src/oryx/install.sh b/src/oryx/install.sh index eeacbec39..8a85fecd1 100755 --- a/src/oryx/install.sh +++ b/src/oryx/install.sh @@ -188,10 +188,10 @@ SOLUTION_FILE_NAME="Oryx.sln" echo "Building solution '$SOLUTION_FILE_NAME'..." cd $GIT_ORYX -${DOTNET_BINARY} build "$SOLUTION_FILE_NAME" -c Debug +${DOTNET_BINARY} build "$SOLUTION_FILE_NAME" -c Debug -p:NuGetAudit=false -${DOTNET_BINARY} publish -property:ValidateExecutableReferencesMatchSelfContained=false -r linux-x64 -o ${BUILD_SCRIPT_GENERATOR} -c Release $GIT_ORYX/src/BuildScriptGeneratorCli/BuildScriptGeneratorCli.csproj --self-contained true -${DOTNET_BINARY} publish -r linux-x64 -o ${BUILD_SCRIPT_GENERATOR} -c Release $GIT_ORYX/src/BuildServer/BuildServer.csproj --self-contained true +${DOTNET_BINARY} publish -p:NuGetAudit=false -property:ValidateExecutableReferencesMatchSelfContained=false -r linux-x64 -o ${BUILD_SCRIPT_GENERATOR} -c Release $GIT_ORYX/src/BuildScriptGeneratorCli/BuildScriptGeneratorCli.csproj --self-contained true +${DOTNET_BINARY} publish -p:NuGetAudit=false -r linux-x64 -o ${BUILD_SCRIPT_GENERATOR} -c Release $GIT_ORYX/src/BuildServer/BuildServer.csproj --self-contained true chmod a+x ${BUILD_SCRIPT_GENERATOR}/GenerateBuildScript From 8431b2dc0714188386f1ee126d8583bd617f30a3 Mon Sep 17 00:00:00 2001 From: Kaniska Date: Tue, 30 Jun 2026 22:57:40 +0530 Subject: [PATCH 68/78] [git] - Fixing issue for alpine installation from source and updating tests (#1679) * [git] - Fixing issue for alpine installation from source and updating tests * Consolidating common logic --- src/git/devcontainer-feature.json | 2 +- src/git/install.sh | 9 +++++++-- test/git/install_git_from_src.sh | 4 ++++ test/git/install_git_from_src_alpine.sh | 4 ++++ test/git/install_git_from_src_bookworm.sh | 1 + test/git/install_git_from_src_centos-7.sh | 16 ---------------- test/git/install_git_from_src_noble.sh | 4 ++++ test/git/install_git_from_src_trixie.sh | 1 + test/git/scenarios.json | 15 ++++++++++++--- test/git/utils.sh | 19 +++++++++++++++++++ 10 files changed, 53 insertions(+), 22 deletions(-) create mode 120000 test/git/install_git_from_src_bookworm.sh delete mode 100644 test/git/install_git_from_src_centos-7.sh create mode 120000 test/git/install_git_from_src_trixie.sh create mode 100644 test/git/utils.sh diff --git a/src/git/devcontainer-feature.json b/src/git/devcontainer-feature.json index 93ea7b061..46b49b27f 100644 --- a/src/git/devcontainer-feature.json +++ b/src/git/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "git", - "version": "1.3.6", + "version": "1.3.7", "name": "Git (from source)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/git", "description": "Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.", diff --git a/src/git/install.sh b/src/git/install.sh index ed20a8ce5..d214e3e32 100755 --- a/src/git/install.sh +++ b/src/git/install.sh @@ -263,10 +263,10 @@ elif [ "${ADJUSTED_ID}" = "alpine" ]; then ${INSTALL_CMD} add --no-cache --update curl grep make zlib-dev # ref. - check_packages asciidoc curl-dev expat-dev g++ gcc openssl-dev pcre2-dev perl-dev perl-error python3-dev tcl tk xmlto + check_packages asciidoc curl-dev expat-dev g++ gcc linux-headers openssl-dev pcre2-dev perl-dev perl-error python3-dev tcl tk xmlto elif [ "${ADJUSTED_ID}" = "rhel" ]; then - check_packages gcc libcurl-devel expat-devel gettext-devel openssl-devel perl-devel zlib-devel cmake pcre2-devel tar gzip ca-certificates + check_packages gcc make libcurl-devel expat-devel gettext-devel openssl-devel perl-devel zlib-devel cmake pcre2-devel tar gzip ca-certificates if ! type curl > /dev/null 2>&1; then check_packages curl fi @@ -325,6 +325,11 @@ if [ "${ADJUSTED_ID}" = "alpine" ]; then git_options+=("NO_GETTEXT=YesPlease") fi make -s "${git_options[@]}" all && make -s "${git_options[@]}" install 2>&1 +build_result=$? rm -rf /tmp/git-${GIT_VERSION} clean_up +if [ "${build_result}" -ne 0 ]; then + echo "(!) Failed to build and install git ${GIT_VERSION}." >&2 + exit 1 +fi echo "Done!" diff --git a/test/git/install_git_from_src.sh b/test/git/install_git_from_src.sh index d0ebaa282..4888c25fb 100644 --- a/test/git/install_git_from_src.sh +++ b/test/git/install_git_from_src.sh @@ -5,8 +5,12 @@ set -e # Optional: Import test library source dev-container-features-test-lib +# Import shared helper functions +source "$(dirname "$0")/utils.sh" + # Definition specific tests check "version" git --version +check "version-is-latest" check_git_is_latest_version check "gettext" dpkg-query -l gettext cd /tmp && git clone https://github.com/devcontainers/feature-starter.git diff --git a/test/git/install_git_from_src_alpine.sh b/test/git/install_git_from_src_alpine.sh index 2a26beec5..a9a9d7d9c 100644 --- a/test/git/install_git_from_src_alpine.sh +++ b/test/git/install_git_from_src_alpine.sh @@ -5,8 +5,12 @@ set -e # Optional: Import test library source dev-container-features-test-lib +# Import shared helper functions +source "$(dirname "$0")/utils.sh" + # Definition specific tests check "version" git --version +check "version-is-latest" check_git_is_latest_version cd /tmp && git clone https://github.com/devcontainers/feature-starter.git cd feature-starter diff --git a/test/git/install_git_from_src_bookworm.sh b/test/git/install_git_from_src_bookworm.sh new file mode 120000 index 000000000..aa0c8ade6 --- /dev/null +++ b/test/git/install_git_from_src_bookworm.sh @@ -0,0 +1 @@ +install_git_from_src.sh \ No newline at end of file diff --git a/test/git/install_git_from_src_centos-7.sh b/test/git/install_git_from_src_centos-7.sh deleted file mode 100644 index 84800b543..000000000 --- a/test/git/install_git_from_src_centos-7.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -e - -# Optional: Import test library -source dev-container-features-test-lib - -# Definition specific tests -check "version" git --version - -cd /tmp && git clone https://github.com/devcontainers/feature-starter.git -cd feature-starter -check "perl" bash -c "git -c grep.patternType=perl grep -q 'a.+b'" - -# Report result -reportResults diff --git a/test/git/install_git_from_src_noble.sh b/test/git/install_git_from_src_noble.sh index 337226fdc..dae12450b 100644 --- a/test/git/install_git_from_src_noble.sh +++ b/test/git/install_git_from_src_noble.sh @@ -5,8 +5,12 @@ set -e # Optional: Import test library source dev-container-features-test-lib +# Import shared helper functions +source "$(dirname "$0")/utils.sh" + # Definition specific tests check "version" git --version +check "latest version" check_git_is_latest_version check "gettext" dpkg-query -l gettext cd /tmp && git clone https://github.com/devcontainers/feature-starter.git diff --git a/test/git/install_git_from_src_trixie.sh b/test/git/install_git_from_src_trixie.sh new file mode 120000 index 000000000..aa0c8ade6 --- /dev/null +++ b/test/git/install_git_from_src_trixie.sh @@ -0,0 +1 @@ +install_git_from_src.sh \ No newline at end of file diff --git a/test/git/scenarios.json b/test/git/scenarios.json index 7feff6564..ce110d3c5 100644 --- a/test/git/scenarios.json +++ b/test/git/scenarios.json @@ -1,6 +1,6 @@ { "install_git_from_src": { - "image": "ubuntu:noble", + "image": "ubuntu:resolute", "features": { "git": { "version": "latest", @@ -53,8 +53,17 @@ } } }, - "install_git_from_src_centos-7": { - "image": "centos:centos7", + "install_git_from_src_bookworm": { + "image": "debian:bookworm", + "features": { + "git": { + "version": "latest", + "ppa": "false" + } + } + }, + "install_git_from_src_trixie": { + "image": "debian:trixie", "features": { "git": { "version": "latest", diff --git a/test/git/utils.sh b/test/git/utils.sh new file mode 100644 index 000000000..6b5b03f93 --- /dev/null +++ b/test/git/utils.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Shared helper functions for git "install from source" test scenarios. + +# Resolves the latest stable git version from GitHub +get_latest_git_version() { + curl -sSL -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/git/git/tags" \ + | grep -oP '"name":\s*"v\K[0-9]+\.[0-9]+\.[0-9]+(?=")' \ + | sort -rV \ + | head -n 1 +} + +# Verifies the installed git version matches the latest stable version on GitHub +check_git_is_latest_version() { + local latest_version installed_version + latest_version="$(get_latest_git_version)" + installed_version="$(git --version | awk '{print $3}')" + [ -n "$latest_version" ] && [ "$installed_version" = "$latest_version" ] +} From f15b529848d77c462d1bf8004c0ff465e124fdd3 Mon Sep 17 00:00:00 2001 From: Daniel Meilak <32960789+daniel-meilak@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:49:12 +0200 Subject: [PATCH 69/78] Fix desktop-lite ALSA package selection (#1676) * Fix desktop-lite ALSA package selection Select the first installable ALSA package by apt candidate availability so Ubuntu releases before and after the t64 transition continue to work. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Order newer packages first Co-authored-by: nicholas Krul * Use symlink for duplicate desktop-lite ALSA test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: nicholas Krul Co-authored-by: Kaniska --- src/desktop-lite/devcontainer-feature.json | 2 +- src/desktop-lite/install.sh | 26 ++++++++++----- test/desktop-lite/check_asound_package.sh | 33 +++++++++++++++++++ test/desktop-lite/scenarios.json | 12 +++++++ test/desktop-lite/test.sh | 22 ++----------- .../test_asound_package_ubuntu_2204.sh | 1 + .../test_asound_package_ubuntu_2604.sh | 14 ++++++++ 7 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 test/desktop-lite/check_asound_package.sh create mode 120000 test/desktop-lite/test_asound_package_ubuntu_2204.sh create mode 100755 test/desktop-lite/test_asound_package_ubuntu_2604.sh diff --git a/src/desktop-lite/devcontainer-feature.json b/src/desktop-lite/devcontainer-feature.json index b87e2bc02..891ac6d73 100644 --- a/src/desktop-lite/devcontainer-feature.json +++ b/src/desktop-lite/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "desktop-lite", - "version": "1.2.9", + "version": "1.2.10", "name": "Light-weight Desktop", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/desktop-lite", "description": "Adds a lightweight Fluxbox based desktop to the container that can be accessed using a VNC viewer or the web. GUI-based commands executed from the built-in VS code terminal will open on the desktop automatically.", diff --git a/src/desktop-lite/install.sh b/src/desktop-lite/install.sh index 822818723..0d6d6e31d 100755 --- a/src/desktop-lite/install.sh +++ b/src/desktop-lite/install.sh @@ -168,6 +168,19 @@ check_packages() { fi } +find_available_package() { + local candidate + local package_name + for package_name in "$@"; do + candidate="$(apt-cache policy "${package_name}" | awk '/Candidate:/ {print $2}')" + if [ -n "${candidate}" ] && [ "${candidate}" != "(none)" ]; then + echo "${package_name}" + return 0 + fi + done + return 1 +} + ########################## # Install starts here # ########################## @@ -199,15 +212,12 @@ fi # Install X11, fluxbox and VS Code dependencies check_packages ${package_list} -# if Ubuntu-24.04, noble(numbat) / Debian-13, trixie found, then will install libasound2-dev instead of libasound2. -# this change is temporary, https://packages.ubuntu.com/noble/libasound2 will switch to libasound2 once it is available for Ubuntu-24.04, noble(numbat) -. /etc/os-release -if { [ "${ID}" = "ubuntu" ] && [ "${VERSION_CODENAME}" = "noble" ]; } || { [ "${ID}" = "debian" ] && [ "${VERSION_CODENAME}" = "trixie" ]; }; then - echo "Detected Noble (Ubuntu 24.04) or Trixie (Debian). Installing libasound2-dev package..." - check_packages "libasound2-dev" -else - check_packages "libasound2" +if ! alsa_package="$(find_available_package libasound2t64 libasound2 libasound2-dev)"; then + echo "(!) No supported ALSA package found. Tried: libasound2, libasound2t64, libasound2-dev." >&2 + exit 1 fi +echo "Installing ${alsa_package} package..." +check_packages "${alsa_package}" # On newer versions of Ubuntu (22.04), # we need an additional package that isn't provided in earlier versions diff --git a/test/desktop-lite/check_asound_package.sh b/test/desktop-lite/check_asound_package.sh new file mode 100644 index 000000000..112dac197 --- /dev/null +++ b/test/desktop-lite/check_asound_package.sh @@ -0,0 +1,33 @@ +checkOSPackage() { + PACKAGE_NAME=$1 + # Check if the package exists and retrieve its exact version + if [ "$(dpkg-query -W -f='${Status}' "$PACKAGE_NAME" 2>/dev/null | grep -c "ok installed")" -eq 1 ]; then + echo "โœ… Package '$PACKAGE_NAME' is installed." + return 0 + else + echo "โŒ Package '$PACKAGE_NAME' is not installed." + return 1 + fi +} + +findAvailableOSPackage() { + local candidate + local package_name + for package_name in "$@"; do + candidate="$(apt-cache policy "${package_name}" | awk '/Candidate:/ {print $2}')" + if [ -n "${candidate}" ] && [ "${candidate}" != "(none)" ]; then + echo "${package_name}" + return 0 + fi + done + return 1 +} + +checkAsoundPackage() { + local alsa_package + if ! alsa_package="$(findAvailableOSPackage libasound2 libasound2t64 libasound2-dev)"; then + echo "No supported ALSA package found in apt indexes." >&2 + exit 1 + fi + check "alsa-package-installed-${alsa_package}" checkOSPackage "${alsa_package}" +} diff --git a/test/desktop-lite/scenarios.json b/test/desktop-lite/scenarios.json index f8719acc6..fcb804ff5 100644 --- a/test/desktop-lite/scenarios.json +++ b/test/desktop-lite/scenarios.json @@ -45,6 +45,18 @@ "desktop-lite": {} } }, + "test_asound_package_ubuntu_2204": { + "image": "ubuntu:22.04", + "features": { + "desktop-lite": {} + } + }, + "test_asound_package_ubuntu_2604": { + "image": "ubuntu:26.04", + "features": { + "desktop-lite": {} + } + }, "test_desktop_init_exec_passthrough": { "image": "ubuntu:noble", "features": { diff --git a/test/desktop-lite/test.sh b/test/desktop-lite/test.sh index 32eab53c4..a22c49532 100755 --- a/test/desktop-lite/test.sh +++ b/test/desktop-lite/test.sh @@ -10,30 +10,14 @@ echoStderr() echo "$@" 1>&2 } -checkOSPackage() { - LABEL=$1 - PACKAGE_NAME=$2 - echo -e "\n๐Ÿงช Testing $LABEL" - # Check if the package exists and retrieve its exact version - if [ "$(dpkg-query -W -f='${Status}' "$PACKAGE_NAME" 2>/dev/null | grep -c "ok installed")" -eq 1 ]; then - echo "โœ… Package '$PACKAGE_NAME' is installed." - exit 0 - else - echo "โŒ Package '$PACKAGE_NAME' is not installed." - exit 1 - fi -} +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${script_dir}/check_asound_package.sh" check "desktop-init-exists" bash -c "ls /usr/local/share/desktop-init.sh" check "log-exists" bash -c "ls /tmp/container-init.log" check "fluxbox-exists" bash -c "ls -la ~/.fluxbox" -. /etc/os-release -if [ "${VERSION_CODENAME}" = "noble" ] || [ "${VERSION_CODENAME}" = "trixie" ]; then - checkOSPackage "if libasound2-dev exists !" "libasound2-dev" -else - checkOSPackage "if libasound2 exists !" "libasound2" -fi +checkAsoundPackage # Report result reportResults \ No newline at end of file diff --git a/test/desktop-lite/test_asound_package_ubuntu_2204.sh b/test/desktop-lite/test_asound_package_ubuntu_2204.sh new file mode 120000 index 000000000..297c107db --- /dev/null +++ b/test/desktop-lite/test_asound_package_ubuntu_2204.sh @@ -0,0 +1 @@ +test_asound_package_ubuntu_2604.sh \ No newline at end of file diff --git a/test/desktop-lite/test_asound_package_ubuntu_2604.sh b/test/desktop-lite/test_asound_package_ubuntu_2604.sh new file mode 100755 index 000000000..150a806ed --- /dev/null +++ b/test/desktop-lite/test_asound_package_ubuntu_2604.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${script_dir}/check_asound_package.sh" + +checkAsoundPackage + +# Report result +reportResults From 6c375f1d65510836760bef052f4614a0df974946 Mon Sep 17 00:00:00 2001 From: Paul Taylor <178183+trxcllnt@users.noreply.github.com> Date: Wed, 8 Jul 2026 04:40:37 -0700 Subject: [PATCH 70/78] Fix installing git-core PPA in Ubuntu 26.04 (#1675) * dearmor git-core ppa key for Ubuntu Resolute * add git-core ppa tests for ubuntu noble and resolute * make new tests into symlinks --------- Co-authored-by: Kaniska --- src/git/devcontainer-feature.json | 2 +- src/git/install.sh | 28 +++++++++++------------ test/git/install_git_from_ppa_noble.sh | 1 + test/git/install_git_from_ppa_resolute.sh | 1 + test/git/install_git_from_src_resolute.sh | 1 + test/git/scenarios.json | 27 ++++++++++++++++++++++ 6 files changed, 45 insertions(+), 15 deletions(-) create mode 120000 test/git/install_git_from_ppa_noble.sh create mode 120000 test/git/install_git_from_ppa_resolute.sh create mode 120000 test/git/install_git_from_src_resolute.sh diff --git a/src/git/devcontainer-feature.json b/src/git/devcontainer-feature.json index 46b49b27f..1aa7e237b 100644 --- a/src/git/devcontainer-feature.json +++ b/src/git/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "git", - "version": "1.3.7", + "version": "1.3.8", "name": "Git (from source)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/git", "description": "Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.", diff --git a/src/git/install.sh b/src/git/install.sh index d214e3e32..e289ce16b 100755 --- a/src/git/install.sh +++ b/src/git/install.sh @@ -110,12 +110,8 @@ get_gpg_key_servers() { # Import the specified key in a variable name passed in as receive_gpg_keys() { - local keys=${!1} - local keyring_args="" - if [ ! -z "$2" ]; then - mkdir -p "$(dirname \"$2\")" - keyring_args="--no-default-keyring --keyring $2" - fi + local -a keys="(${!1})" + mkdir -p "$(dirname "$2")" # Install curl if ! type curl > /dev/null 2>&1; then @@ -133,13 +129,17 @@ receive_gpg_keys() { set +e until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; do - echo "(*) Downloading GPG key..." - ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true" - if [ "${gpg_ok}" != "true" ]; then - echo "(*) Failed getting key, retrying in 10s..." - (( retry_count++ )) - sleep 10s - fi + for key in "${keys[@]}"; do + echo "(*) Downloading GPG key '${key}'..." + gpg --recv-keys "${key}" \ + && gpg --export "${key}" | gpg --dearmor --yes -o "$2" \ + && gpg_ok="true" + if [ "${gpg_ok}" != "true" ]; then + echo "(*) Failed getting key, retrying in 10s..." + (( retry_count++ )) + sleep 10s + fi + done done set -e if [ "${gpg_ok}" = "false" ]; then @@ -275,7 +275,7 @@ elif [ "${ADJUSTED_ID}" = "rhel" ]; then fi if ! type awk > /dev/null 2>&1; then check_packages gawk - fi + fi if [ $ID = "mariner" ]; then check_packages glibc-devel kernel-headers binutils fi diff --git a/test/git/install_git_from_ppa_noble.sh b/test/git/install_git_from_ppa_noble.sh new file mode 120000 index 000000000..408beff37 --- /dev/null +++ b/test/git/install_git_from_ppa_noble.sh @@ -0,0 +1 @@ +install_git_from_ppa_jammy.sh \ No newline at end of file diff --git a/test/git/install_git_from_ppa_resolute.sh b/test/git/install_git_from_ppa_resolute.sh new file mode 120000 index 000000000..408beff37 --- /dev/null +++ b/test/git/install_git_from_ppa_resolute.sh @@ -0,0 +1 @@ +install_git_from_ppa_jammy.sh \ No newline at end of file diff --git a/test/git/install_git_from_src_resolute.sh b/test/git/install_git_from_src_resolute.sh new file mode 120000 index 000000000..1e44e792a --- /dev/null +++ b/test/git/install_git_from_src_resolute.sh @@ -0,0 +1 @@ +install_git_from_src_noble.sh \ No newline at end of file diff --git a/test/git/scenarios.json b/test/git/scenarios.json index ce110d3c5..d47f0bb6e 100644 --- a/test/git/scenarios.json +++ b/test/git/scenarios.json @@ -44,6 +44,33 @@ } } }, + "install_git_from_ppa_noble": { + "image": "ubuntu:noble", + "features": { + "git": { + "version": "latest", + "ppa": "true" + } + } + }, + "install_git_from_src_resolute": { + "image": "ubuntu:resolute", + "features": { + "git": { + "version": "latest", + "ppa": "false" + } + } + }, + "install_git_from_ppa_resolute": { + "image": "ubuntu:resolute", + "features": { + "git": { + "version": "latest", + "ppa": "true" + } + } + }, "install_git_from_src_bullseye": { "image": "debian:bullseye", "features": { From 0f547996943a66f51c28cae151fec87907b27ee9 Mon Sep 17 00:00:00 2001 From: Bobby Reynolds <37971212+reynoldsbd@users.noreply.github.com> Date: Tue, 14 Jul 2026 06:51:42 -0700 Subject: [PATCH 71/78] fix(rust): support Azure Linux base images (#1685) --- src/rust/NOTES.md | 4 ++-- src/rust/README.md | 4 ++-- src/rust/devcontainer-feature.json | 2 +- src/rust/install.sh | 7 ++++--- test/rust/rust_with_azurelinux.sh | 31 ++++++++++++++++++++++++++++++ test/rust/scenarios.json | 10 ++++++++++ 6 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 test/rust/rust_with_azurelinux.sh diff --git a/src/rust/NOTES.md b/src/rust/NOTES.md index 1f01e6e52..68d170302 100644 --- a/src/rust/NOTES.md +++ b/src/rust/NOTES.md @@ -2,8 +2,8 @@ ## OS Support -This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, Alma, RockyLinux -and Mariner distributions with the `apt`, `yum`, `dnf`, `microdnf` and `tdnf` package manager installed. +This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, Alma, RockyLinux, +Mariner and Azure Linux distributions with the `apt`, `yum`, `dnf`, `microdnf` and `tdnf` package manager installed. **Note:** Alpine is not supported because the rustup-init binary requires glibc to run, but Alpine Linux does not include `glibc` diff --git a/src/rust/README.md b/src/rust/README.md index ef0bc2311..eca22932c 100644 --- a/src/rust/README.md +++ b/src/rust/README.md @@ -32,8 +32,8 @@ Installs Rust, common Rust utilities, and their required dependencies ## OS Support -This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, Alma, RockyLinux -and Mariner distributions with the `apt`, `yum`, `dnf`, `microdnf` and `tdnf` package manager installed. +This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, Alma, RockyLinux, +Mariner and Azure Linux distributions with the `apt`, `yum`, `dnf`, `microdnf` and `tdnf` package manager installed. **Note:** Alpine is not supported because the rustup-init binary requires glibc to run, but Alpine Linux does not include `glibc` diff --git a/src/rust/devcontainer-feature.json b/src/rust/devcontainer-feature.json index 88b64daed..d8d399cde 100644 --- a/src/rust/devcontainer-feature.json +++ b/src/rust/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "rust", - "version": "1.5.0", + "version": "1.5.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 99a7ba8f5..56bb35ea8 100755 --- a/src/rust/install.sh +++ b/src/rust/install.sh @@ -30,7 +30,7 @@ if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then ADJUSTED_ID="debian" elif [ "${ID}" = "alpine" ]; then ADJUSTED_ID="alpine" -elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"mariner"* ]]; then +elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "azurelinux" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"azurelinux"* || "${ID_LIKE}" = *"mariner"* ]]; then ADJUSTED_ID="rhel" VERSION_CODENAME="${ID}${VERSION_ID}" else @@ -264,6 +264,7 @@ check_packages() { "python3-minimal") packages[$i]="python3" ;; "libpython3.*") packages[$i]="python3-devel" ;; "gnupg2") packages[$i]="gnupg" ;; + "passwd") packages[$i]="shadow-utils" ;; esac ;; esac @@ -303,7 +304,7 @@ export DEBIAN_FRONTEND=noninteractive # Install curl, lldb, python3-minimal,libpython and rust dependencies if missing echo "Installing required dependencies..." -check_packages curl ca-certificates gcc libc6-dev gnupg2 git +check_packages curl ca-certificates gcc libc6-dev gnupg2 git passwd # Install optional dependencies (continue if they fail) case "$PKG_MANAGER" in @@ -315,7 +316,7 @@ case "$PKG_MANAGER" in ;; tdnf) check_packages python3 python3-devel || true - # LLDB might not be available in Photon/Mariner + # LLDB might not be available in Photon/Mariner/Azure Linux ;; esac diff --git a/test/rust/rust_with_azurelinux.sh b/test/rust/rust_with_azurelinux.sh new file mode 100644 index 000000000..ac940136f --- /dev/null +++ b/test/rust/rust_with_azurelinux.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Helper function to check component is installed +check_component_installed() { + local component=$1 + if rustup component list | grep -q "${component}.*installed"; then + return 0 # Component is installed (success) + else + return 1 # Component is not installed (failure) + fi +} + +# Definition specific tests +check "cargo version" cargo --version +check "rustc version" rustc --version +check "correct rust version" rustup target list | grep aarch64-unknown-linux-gnu + +# Check that all specified extended components are installed +check "rust-analyzer is installed" check_component_installed "rust-analyzer" +check "rust-src is installed" check_component_installed "rust-src" +check "rustfmt is installed" check_component_installed "rustfmt" +check "clippy is installed" check_component_installed "clippy" +check "rust-docs is installed" check_component_installed "rust-docs" + +# Report result +reportResults \ No newline at end of file diff --git a/test/rust/scenarios.json b/test/rust/scenarios.json index 21e347947..87c93bfa7 100644 --- a/test/rust/scenarios.json +++ b/test/rust/scenarios.json @@ -134,5 +134,15 @@ "components": "rust-analyzer,rust-src,rustfmt,clippy,rust-docs" } } + }, + "rust_with_azurelinux": { + "image": "mcr.microsoft.com/azurelinux/base/core:3.0", + "features": { + "rust": { + "version": "latest", + "targets": "aarch64-unknown-linux-gnu", + "components": "rust-analyzer,rust-src,rustfmt,clippy,rust-docs" + } + } } } From 765e8ebd8f8012fb740cd7b41483a745bcedd212 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:56:43 +0100 Subject: [PATCH 72/78] Fix terraform feature OpenPGP error on Ubuntu 26.04 (resolute) (#1683) * Initial plan * Extend GPG workaround to Ubuntu 26.04 (resolute) and add tests * Use devcontainers base:resolute image for terraform resolute tests * Bump terraform feature version to 1.4.4 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/terraform/devcontainer-feature.json | 2 +- src/terraform/install.sh | 6 +++--- test/terraform/install_in_ubuntu_resolute.sh | 17 ++++++++++++++++ .../install_in_ubuntu_resolute_sentinel.sh | 20 +++++++++++++++++++ test/terraform/scenarios.json | 16 +++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100755 test/terraform/install_in_ubuntu_resolute.sh create mode 100755 test/terraform/install_in_ubuntu_resolute_sentinel.sh diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index f9ebcbee8..37a60db26 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.4.3", + "version": "1.4.4", "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 8c4755ebe..8bc79107c 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -19,8 +19,8 @@ INSTALL_SENTINEL=${INSTALLSENTINEL:-false} INSTALL_TFSEC=${INSTALLTFSEC:-false} INSTALL_TERRAFORM_DOCS=${INSTALLTERRAFORMDOCS:-false} CUSTOM_DOWNLOAD_SERVER="${CUSTOMDOWNLOADSERVER:-""}" -# This is because ubuntu noble and debian trixie don't support the old format of GPG keys and validation -NEW_GPG_CODENAMES="trixie noble" +# This is because ubuntu noble, ubuntu resolute and debian trixie don't support the old format of GPG keys and validation +NEW_GPG_CODENAMES="trixie noble resolute" TERRAFORM_SHA256="${TERRAFORM_SHA256:-"automatic"}" TFLINT_SHA256="${TFLINT_SHA256:-"automatic"}" @@ -52,7 +52,7 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi -# Detect Ubuntu Noble or Debian Trixie and use new repo setup, else use legacy GPG logic +# Detect Ubuntu Noble, Ubuntu Resolute or Debian Trixie and use new repo setup, else use legacy GPG logic IS_GPG_NEW=0 . /etc/os-release if [[ "${NEW_GPG_CODENAMES}" == *"${VERSION_CODENAME}"* ]]; then diff --git a/test/terraform/install_in_ubuntu_resolute.sh b/test/terraform/install_in_ubuntu_resolute.sh new file mode 100755 index 000000000..8fa4cacb9 --- /dev/null +++ b/test/terraform/install_in_ubuntu_resolute.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Import test library +source dev-container-features-test-lib + +# Check to make sure the user is vscode +check "user is vscode" whoami | grep vscode + +# Check if terraform was installed correctly +check "terraform installed" terraform --version + +check "tflint" tflint --version + +# Report results +reportResults diff --git a/test/terraform/install_in_ubuntu_resolute_sentinel.sh b/test/terraform/install_in_ubuntu_resolute_sentinel.sh new file mode 100755 index 000000000..32c76bbe4 --- /dev/null +++ b/test/terraform/install_in_ubuntu_resolute_sentinel.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Check to make sure the user is vscode +check "user is vscode" whoami | grep vscode + +# Check if terraform was installed correctly +check "terraform installed" terraform --version + +check "tflint" tflint --version + +# Sentinel specific tests +check "sentinel" sentinel --version + +# Report result +reportResults diff --git a/test/terraform/scenarios.json b/test/terraform/scenarios.json index 09bb0f598..393bd3303 100644 --- a/test/terraform/scenarios.json +++ b/test/terraform/scenarios.json @@ -31,6 +31,22 @@ } } }, + "install_in_ubuntu_resolute": { + "image": "mcr.microsoft.com/devcontainers/base:resolute", + "features": { + "terraform": { + "version": "latest" + } + } + }, + "install_in_ubuntu_resolute_sentinel": { + "image": "mcr.microsoft.com/devcontainers/base:resolute", + "features": { + "terraform": { + "installSentinel": true + } + } + }, "install_sentinel": { "image": "mcr.microsoft.com/devcontainers/base:jammy", "features": { From 40aeb53ad77842bc30c9a1adb9ad28a41b753153 Mon Sep 17 00:00:00 2001 From: Venkumahanti Subhankar Date: Tue, 28 Jul 2026 15:49:25 +0530 Subject: [PATCH 73/78] docs(node): document pre-bundled items and how to opt out (#1695) * docs(node): document pre-bundled items and how to opt out Added notes on excluding pre-bundled items and VS Code extensions. * Fix formatting in NOTES.md Corrected formatting and punctuation in the NOTES.md file. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/node/NOTES.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/node/NOTES.md b/src/node/NOTES.md index 506fa1b4e..9f1c1d3bc 100644 --- a/src/node/NOTES.md +++ b/src/node/NOTES.md @@ -25,3 +25,27 @@ Debian/Ubuntu, RedHat Enterprise Linux, Fedora, Alma, and Rocky Linux distributi **Note**: RedHat 7 Family (RedHat, CentOS, etc.) must use Node versions less than 18 due to its system libraries and long-term support (LTS) policies. `bash` is required to execute the `install.sh` script. + +## Pre-bundled items + +> [!NOTE] +> Beyond the core install, this feature also sets up a few items by default for convenience โ€” recommended VS Code extensions (such as a linter) and supporting tools. This is intentional behavior shared across features in this repository. + +## Excluding pre-bundled items + +Exclude a bundled **VS Code extension** by prefixing its ID with `-`, or (when supported by a feature option) disable a bundled **tool** by setting its version option to `none` (for example, `pnpmVersion`: `none`): + +```json +{ + "features": { + "ghcr.io/devcontainers/features/node:2": { + "pnpmVersion": "none" + } + }, + "customizations": { + "vscode": { + "extensions": [ "-dbaeumer.vscode-eslint" ] + } + } +} +``` From 529a88d08e10671b5e61c48ade6904a02b867779 Mon Sep 17 00:00:00 2001 From: Venkumahanti Subhankar Date: Tue, 28 Jul 2026 15:51:09 +0530 Subject: [PATCH 74/78] fix(nix): Resolve PATH mismatch for packages option in multi-user mode (#1691) * fix(nix): Resolve PATH mismatch for packages option in multi-user mode * fix(nix): align package installs with the active multi-user profile --- src/nix/install.sh | 6 ++---- src/nix/post-install-steps.sh | 14 +++++++++++--- test/nix/packages.sh | 1 + test/nix/scenarios.json | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/nix/install.sh b/src/nix/install.sh index 0030c2b18..ca19f658a 100755 --- a/src/nix/install.sh +++ b/src/nix/install.sh @@ -113,10 +113,8 @@ fi chmod +x,o+r ${FEATURE_DIR} ${FEATURE_DIR}/post-install-steps.sh if [ "${MULTIUSER}" = "true" ]; then /usr/local/share/nix-entrypoint.sh - su ${USERNAME} -c " - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh - ${FEATURE_DIR}/post-install-steps.sh - " + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + NIX_FEATURE_INSTALL_PROFILE=/nix/var/nix/profiles/default ${FEATURE_DIR}/post-install-steps.sh else su ${USERNAME} -c " . \$HOME/.nix-profile/etc/profile.d/nix.sh diff --git a/src/nix/post-install-steps.sh b/src/nix/post-install-steps.sh index 68f93a391..94cfed8a3 100755 --- a/src/nix/post-install-steps.sh +++ b/src/nix/post-install-steps.sh @@ -2,6 +2,14 @@ set -e echo "(*) Executing post-installation steps..." +# In multi-user mode, install into the default profile that is on PATH. +NIX_ENV_PROFILE_ARGS=() +NIX_PROFILE_INSTALL_ARGS=() +if [ -n "${NIX_FEATURE_INSTALL_PROFILE}" ]; then + NIX_ENV_PROFILE_ARGS=(-p "${NIX_FEATURE_INSTALL_PROFILE}") + NIX_PROFILE_INSTALL_ARGS=(--profile "${NIX_FEATURE_INSTALL_PROFILE}") +fi + # if not starts with "nixpkgs." add it as prefix to package name add_nixpkgs_prefix() { local packages=$1 @@ -20,17 +28,17 @@ if [ ! -z "${PACKAGES}" ] && [ "${PACKAGES}" != "none" ]; then if [ "${USEATTRIBUTEPATH}" = "true" ]; then PACKAGES=$(add_nixpkgs_prefix "$PACKAGES") echo "Installing packages \"${PACKAGES}\" in profile..." - nix-env -iA ${PACKAGES} + nix-env "${NIX_ENV_PROFILE_ARGS[@]}" -iA ${PACKAGES} else echo "Installing packages \"${PACKAGES}\" in profile..." - nix-env --install ${PACKAGES} + nix-env "${NIX_ENV_PROFILE_ARGS[@]}" --install ${PACKAGES} fi fi # Install Nix flake in profile if specified if [ ! -z "${FLAKEURI}" ] && [ "${FLAKEURI}" != "none" ]; then echo "Installing flake ${FLAKEURI} in profile..." - nix profile install "${FLAKEURI}" + nix profile install "${NIX_PROFILE_INSTALL_ARGS[@]}" "${FLAKEURI}" fi nix-collect-garbage --delete-old diff --git a/test/nix/packages.sh b/test/nix/packages.sh index ad896e9e0..c59a6e6d9 100755 --- a/test/nix/packages.sh +++ b/test/nix/packages.sh @@ -29,6 +29,7 @@ check "nix-env" type nix-env check "vim_installed" type vim check "node_installed" type node check "yarn_installed" type yarn +check "vim_in_default_profile" bash -lc "nix-env -p /nix/var/nix/profiles/default -q | grep -q '^vim'" # Report result # If any of the checks above exited with a non-zero exit code, the test will fail. diff --git a/test/nix/scenarios.json b/test/nix/scenarios.json index 38eed0268..3fb839629 100644 --- a/test/nix/scenarios.json +++ b/test/nix/scenarios.json @@ -86,7 +86,7 @@ "remoteUser": "vscode", "features": { "nix": { - "packages": "nodePackages.nodejs,nixpkgs.vim,nixpkgs.yarn", + "packages": "nodejs,nixpkgs.vim,nixpkgs.yarn", "useAttributePath": true } } From 99a3f1c39fb6771640100bfcbbb4ef5de1e4aa1e Mon Sep 17 00:00:00 2001 From: Venkumahanti Subhankar Date: Tue, 28 Jul 2026 16:20:33 +0530 Subject: [PATCH 75/78] fix(java): Add retry logic for transient SDKMAN bootstrap failures (#1688) * Implement retry logic for SDK installation Added a retry mechanism for SDK installation and SDKMAN CLI installation. * Update Java version from 1.8.0 to 1.8.1 Mandatory minor bump for fixing the sdkman transient error * Update expected Java version in installation script Java version updated due to upstream update from sdkman. * Fix string quotes in install_latest_version.sh * Change curl option from -sSL to -fsSL in install.sh --- src/java/devcontainer-feature.json | 2 +- src/java/install.sh | 28 ++++++++++++++++++++++++++-- test/java/install_latest_version.sh | 4 ++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/java/devcontainer-feature.json b/src/java/devcontainer-feature.json index c82718a49..3a1170df7 100644 --- a/src/java/devcontainer-feature.json +++ b/src/java/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "java", - "version": "1.8.0", + "version": "1.8.1", "name": "Java (via SDKMAN!)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/java", "description": "Installs Java, SDKMAN! (if not installed), and needed dependencies.", diff --git a/src/java/install.sh b/src/java/install.sh index 988460307..a142ab9e3 100644 --- a/src/java/install.sh +++ b/src/java/install.sh @@ -195,6 +195,29 @@ updaterc() { fi } +run_with_retries() { + local max_attempts="$1" + local wait_seconds="$2" + local operation="$3" + local attempt=1 + shift 3 + + until "$@"; do + if [ "${attempt}" -ge "${max_attempts}" ]; then + echo "(!) ${operation} failed after ${max_attempts} attempts." + return 1 + fi + + echo "(*) ${operation} failed on attempt ${attempt}. Retrying in ${wait_seconds}s..." + attempt=$((attempt + 1)) + sleep "${wait_seconds}" + done +} + +install_sdkman_cli() { + bash -o pipefail -c 'curl -fsSL "https://get.sdkman.io?rcupdate=false" | bash' +} + find_version_list() { prefix="$1" suffix="$2" @@ -284,7 +307,8 @@ sdk_install() { JAVA_VERSION=${requested_version} fi - su ${USERNAME} -c "umask 0002 && . ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk install ${install_type} ${requested_version} && sdk flush archives && sdk flush temp" + run_with_retries 5 10 "Installing ${install_type} ${requested_version} via SDKMAN" \ + su ${USERNAME} -c "umask 0002 && . ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk install ${install_type} ${requested_version} && sdk flush archives && sdk flush temp" } export DEBIAN_FRONTEND=noninteractive @@ -323,7 +347,7 @@ if [ ! -d "${SDKMAN_DIR}" ]; then if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${MAJOR_VERSION_ID}" = "8" ]; then export SDKMAN_NATIVE_VERSION="false" fi - curl -sSL "https://get.sdkman.io?rcupdate=false" | bash + run_with_retries 5 10 "Installing SDKMAN" install_sdkman_cli # For RHEL 8 systems, also disable native CLI in config file and remove native binaries if [ "${ADJUSTED_ID}" = "rhel" ] && [ "${MAJOR_VERSION_ID}" = "8" ]; then # Disable native CLI in config to prevent future usage diff --git a/test/java/install_latest_version.sh b/test/java/install_latest_version.sh index 03175e767..2c8852970 100644 --- a/test/java/install_latest_version.sh +++ b/test/java/install_latest_version.sh @@ -8,8 +8,8 @@ source dev-container-features-test-lib echo 'public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }' > HelloWorld.java javac HelloWorld.java -check "hello world" /bin/bash -c "java HelloWorld | grep "Hello, World!"" -check "java version latest installed" grep "25" <(java --version) +check "hello world" /bin/bash -c 'java HelloWorld | grep "Hello, World!"' +check "java version latest installed" grep "26" <(java --version) # Report result reportResults From 4170bca4d08a2be5d96a33251a45a0027cc5cacb Mon Sep 17 00:00:00 2001 From: Kazuma Watanabe Date: Tue, 28 Jul 2026 23:03:35 +0900 Subject: [PATCH 76/78] terraform: Add support for GitHub Attestations in TFLint installation (#1589) * terraform: Add support for GitHub Attestations in TFLint installation * terraform: Pin TFLint version in tflint_fallback_test * terraform: Bump version to 1.4.3 * terraform: Update tflint_fallback_test for the latest version --------- Co-authored-by: Kaniska Co-authored-by: Abdurrahmaan Iqbal --- src/terraform/devcontainer-feature.json | 7 +- src/terraform/install.sh | 70 ++++++++++++++------ test/terraform/scenarios.json | 4 +- test/terraform/tflint_fallback_test.sh | 85 ++++++++++++++++--------- 4 files changed, 114 insertions(+), 52 deletions(-) diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 37a60db26..29d3efb30 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.4.4", + "version": "1.4.5", "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.", @@ -79,6 +79,11 @@ } } }, + "dependsOn": { + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "latest" + } + }, "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" ] diff --git a/src/terraform/install.sh b/src/terraform/install.sh index 8bc79107c..ef0c73e5d 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -460,6 +460,23 @@ install_tflint() { curl -sSL -o /tmp/tf-downloads/${TFLINT_FILENAME} https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/${TFLINT_FILENAME} } +verify_tflint_attestations() { + local checksums=$1 + local checksums_sha256=$(sha256sum "$checksums" | cut -d " " -f 1) + + check_packages jq + + curl -L -f "https://api.github.com/repos/terraform-linters/tflint/attestations/sha256:${checksums_sha256}" > attestation.json + curl_exit_code=$? + if [ $curl_exit_code -ne 0 ]; then + echo "(*) Failed to fetch GitHub Attestations for tflint checksums" + return 1 + fi + + jq ".attestations[].bundle" attestation.json > bundle.jsonl + gh at verify "$checksums" -R terraform-linters/tflint -b bundle.jsonl +} + if [ "${TFLINT_VERSION}" != "none" ]; then echo "Downloading tflint..." TFLINT_FILENAME="tflint_linux_${architecture}.zip" @@ -475,31 +492,44 @@ if [ "${TFLINT_VERSION}" != "none" ]; then else curl -sSL -o tflint_checksums.txt https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt + # Attempt GitHub Attestation verification (0.51.1+) set +e - curl -sSL -o checksums.txt.keyless.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.keyless.sig + verify_tflint_attestations tflint_checksums.txt + verify_result=$? set -e - # Check that checksums.txt.keyless.sig exists and is not empty - if [ -s checksums.txt.keyless.sig ]; then - # Validate checksums with cosign - curl -sSL -o checksums.txt.pem https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.pem - ensure_cosign - cosign verify-blob \ - --certificate=/tmp/tf-downloads/checksums.txt.pem \ - --signature=/tmp/tf-downloads/checksums.txt.keyless.sig \ - --certificate-identity-regexp="^https://github.com/terraform-linters/tflint" \ - --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ - /tmp/tf-downloads/tflint_checksums.txt - # Ensure that checksums.txt has $TFLINT_FILENAME - grep ${TFLINT_FILENAME} /tmp/tf-downloads/tflint_checksums.txt - # Validate downloaded file + if [ $verify_result -eq 0 ]; then sha256sum --ignore-missing -c tflint_checksums.txt + echo "(*) tflint_checksums.txt verified successfully using GitHub Attestation." else - # Fallback to older, GPG-based verification (pre-0.47.0 of tflint) - curl -sSL -o tflint_checksums.txt.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.sig - curl -sSL -o tflint_key "${TFLINT_GPG_KEY_URI}" - gpg -q --import tflint_key - gpg --verify tflint_checksums.txt.sig tflint_checksums.txt + # Fallback to cosign verification + echo "(*) GitHub Attestation verification failed or not supported for this version, falling back to Cosign verification..." + set +e + curl -sSL -o checksums.txt.keyless.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.keyless.sig + set -e + + # Check that checksums.txt.keyless.sig exists and is not empty + if [ -s checksums.txt.keyless.sig ]; then + # Validate checksums with cosign + curl -sSL -o checksums.txt.pem https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.pem + ensure_cosign + cosign verify-blob \ + --certificate=/tmp/tf-downloads/checksums.txt.pem \ + --signature=/tmp/tf-downloads/checksums.txt.keyless.sig \ + --certificate-identity-regexp="^https://github.com/terraform-linters/tflint" \ + --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ + /tmp/tf-downloads/tflint_checksums.txt + # Ensure that checksums.txt has $TFLINT_FILENAME + grep ${TFLINT_FILENAME} /tmp/tf-downloads/tflint_checksums.txt + # Validate downloaded file + sha256sum --ignore-missing -c tflint_checksums.txt + else + # Fallback to older, GPG-based verification (pre-0.47.0 of tflint) + curl -sSL -o tflint_checksums.txt.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.sig + curl -sSL -o tflint_key "${TFLINT_GPG_KEY_URI}" + gpg -q --import tflint_key + gpg --verify tflint_checksums.txt.sig tflint_checksums.txt + fi fi fi fi diff --git a/test/terraform/scenarios.json b/test/terraform/scenarios.json index 393bd3303..796efbd3e 100644 --- a/test/terraform/scenarios.json +++ b/test/terraform/scenarios.json @@ -14,7 +14,7 @@ "installSentinel": true } } - }, + }, "install_in_ubuntu_noble": { "image": "mcr.microsoft.com/devcontainers/base:noble", "features": { @@ -138,4 +138,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/terraform/tflint_fallback_test.sh b/test/terraform/tflint_fallback_test.sh index 5619ff4fb..33b75f3b0 100644 --- a/test/terraform/tflint_fallback_test.sh +++ b/test/terraform/tflint_fallback_test.sh @@ -22,7 +22,6 @@ keyserver hkps://keys.openpgp.org keyserver hkps://keyserver.pgp.com" check "tflint version as installed by feature" tflint --version -check "cosign version as installed by feature" cosign version architecture="$(uname -m)" case ${architecture} in @@ -221,14 +220,31 @@ install_tflint() { curl -sSL -o /tmp/tf-downloads/${TFLINT_FILENAME} https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/${TFLINT_FILENAME} } +verify_tflint_attestations() { + local checksums=$1 + local checksums_sha256=$(sha256sum "$checksums" | cut -d " " -f 1) -try_install_dummy_tflint_cosign_version() { + check_packages jq + + curl -L -f "https://api.github.com/repos/terraform-linters/tflint/attestations/sha256:${checksums_sha256}" > attestation.json + curl_exit_code=$? + if [ $curl_exit_code -ne 0 ]; then + echo "(*) Failed to fetch GitHub Attestations for tflint checksums" + return 1 + fi + + jq ".attestations[].bundle" attestation.json > bundle.jsonl + gh at verify "$checksums" -R terraform-linters/tflint -b bundle.jsonl +} + + +try_install_dummy_tflint_version() { mode=$1 tflint_url='https://github.com/terraform-linters/tflint' mkdir -p /tmp/tf-downloads cd /tmp/tf-downloads echo -e "\nTrying to install dummy tflint version..." - TFLINT_VERSION="0.50.XYZ" + TFLINT_VERSION="0.60.XYZ" echo "Downloading tflint...v${TFLINT_VERSION}" TFLINT_FILENAME="tflint_linux_${architecture}.zip" install_tflint "$TFLINT_VERSION" @@ -237,37 +253,50 @@ try_install_dummy_tflint_cosign_version() { fi if [ "${TFLINT_SHA256}" != "dev-mode" ]; then - if [ "${TFLINT_SHA256}" != "automatic" ]; then + if [ "${TFLINT_SHA256}" != "automatic" ]; then echo "${TFLINT_SHA256} *${TFLINT_FILENAME}" > tflint_checksums.txt sha256sum --ignore-missing -c tflint_checksums.txt else curl -sSL -o tflint_checksums.txt https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt + # Attempt GitHub Attestation verification (0.51.1+) set +e - curl -sSL -o checksums.txt.keyless.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.keyless.sig + verify_tflint_attestations tflint_checksums.txt + verify_result=$? set -e - - # Check that checksums.txt.keyless.sig exists and is not empty - if [ -s checksums.txt.keyless.sig ]; then - # Validate checksums with cosign - curl -sSL -o checksums.txt.pem https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.pem - ensure_cosign $mode - cosign verify-blob \ - --certificate=/tmp/tf-downloads/checksums.txt.pem \ - --signature=/tmp/tf-downloads/checksums.txt.keyless.sig \ - --certificate-identity-regexp="^https://github.com/terraform-linters/tflint" \ - --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ - /tmp/tf-downloads/tflint_checksums.txt - # Ensure that checksums.txt has $TFLINT_FILENAME - grep ${TFLINT_FILENAME} /tmp/tf-downloads/tflint_checksums.txt - # Validate downloaded file + + if [ $verify_result -eq 0 ]; then sha256sum --ignore-missing -c tflint_checksums.txt + echo "(*) tflint_checksums.txt verified successfully using GitHub Attestation." else - # Fallback to older, GPG-based verification (pre-0.47.0 of tflint) - curl -sSL -o tflint_checksums.txt.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.sig - curl -sSL -o tflint_key "${TFLINT_GPG_KEY_URI}" - gpg -q --import tflint_key - gpg --verify tflint_checksums.txt.sig tflint_checksums.txt + # Fallback to cosign verification + echo "(*) GitHub Attestation verification failed or not supported for this version, falling back to Cosign verification..." + set +e + curl -sSL -o checksums.txt.keyless.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.keyless.sig + set -e + + # Check that checksums.txt.keyless.sig exists and is not empty + if [ -s checksums.txt.keyless.sig ]; then + # Validate checksums with cosign + curl -sSL -o checksums.txt.pem https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.pem + ensure_cosign $mode + cosign verify-blob \ + --certificate=/tmp/tf-downloads/checksums.txt.pem \ + --signature=/tmp/tf-downloads/checksums.txt.keyless.sig \ + --certificate-identity-regexp="^https://github.com/terraform-linters/tflint" \ + --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ + /tmp/tf-downloads/tflint_checksums.txt + # Ensure that checksums.txt has $TFLINT_FILENAME + grep ${TFLINT_FILENAME} /tmp/tf-downloads/tflint_checksums.txt + # Validate downloaded file + sha256sum --ignore-missing -c tflint_checksums.txt + else + # Fallback to older, GPG-based verification (pre-0.47.0 of tflint) + curl -sSL -o tflint_checksums.txt.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.sig + curl -sSL -o tflint_key "${TFLINT_GPG_KEY_URI}" + gpg -q --import tflint_key + gpg --verify tflint_checksums.txt.sig tflint_checksums.txt + fi fi fi fi @@ -276,12 +305,10 @@ try_install_dummy_tflint_cosign_version() { sudo mv -f tflint /usr/local/bin/ } -try_install_dummy_tflint_cosign_version "mode1" +try_install_dummy_tflint_version "mode1" check "tflint version as installed when mode=1" tflint --version -check "cosign version as installed when mode=1" cosign version -try_install_dummy_tflint_cosign_version "mode2" +try_install_dummy_tflint_version "mode2" check "tflint version as installed when mode=2" tflint --version -check "cosign version as installed when mode=2" cosign version \ No newline at end of file From 73e636fb4f05942f8ebe6e9d685715d5466bf885 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:59:51 +0100 Subject: [PATCH 77/78] Support version pinning for terraform-docs in the Terraform feature (#1698) * Initial plan * Add terraformDocsVersion option to pin terraform-docs version --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/terraform/README.md | 1 + src/terraform/devcontainer-feature.json | 12 +++++++++++- src/terraform/install.sh | 2 +- .../install_terraform_docs_version.sh | 18 ++++++++++++++++++ test/terraform/scenarios.json | 9 +++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 test/terraform/install_terraform_docs_version.sh diff --git a/src/terraform/README.md b/src/terraform/README.md index 4b37b4260..8cfb673dc 100644 --- a/src/terraform/README.md +++ b/src/terraform/README.md @@ -21,6 +21,7 @@ Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects la | installSentinel | Install sentinel, a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions | boolean | false | | installTFsec | Install tfsec, a tool to spot potential misconfigurations for your terraform code | boolean | false | | installTerraformDocs | Install terraform-docs, a utility to generate documentation from Terraform modules | boolean | false | +| terraformDocsVersion | terraform-docs version to install (only used when installTerraformDocs is true) (https://github.com/terraform-docs/terraform-docs/releases) | string | latest | | httpProxy | Connect to a keyserver using a proxy by configuring this option | string | - | | customDownloadServer | Custom server URL for downloading Terraform and Sentinel packages, including protocol (e.g., https://releases.hashicorp.com). If not provided, the default HashiCorp download server (https://releases.hashicorp.com) will be used. | string | - | diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 29d3efb30..634d865ef 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.4.5", + "version": "1.5.0", "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.", @@ -50,6 +50,16 @@ "default": false, "description": "Install terraform-docs, a utility to generate documentation from Terraform modules" }, + "terraformDocsVersion": { + "type": "string", + "proposals": [ + "latest", + "0.20.0", + "0.19.0" + ], + "default": "latest", + "description": "terraform-docs version to install (only used when installTerraformDocs is true) (https://github.com/terraform-docs/terraform-docs/releases)" + }, "httpProxy": { "type": "string", "default": "", diff --git a/src/terraform/install.sh b/src/terraform/install.sh index ef0c73e5d..43779f825 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -18,6 +18,7 @@ TERRAGRUNT_VERSION="${TERRAGRUNT:-"latest"}" INSTALL_SENTINEL=${INSTALLSENTINEL:-false} INSTALL_TFSEC=${INSTALLTFSEC:-false} INSTALL_TERRAFORM_DOCS=${INSTALLTERRAFORMDOCS:-false} +TERRAFORM_DOCS_VERSION="${TERRAFORMDOCSVERSION:-"latest"}" CUSTOM_DOWNLOAD_SERVER="${CUSTOMDOWNLOADSERVER:-""}" # This is because ubuntu noble, ubuntu resolute and debian trixie don't support the old format of GPG keys and validation NEW_GPG_CODENAMES="trixie noble resolute" @@ -641,7 +642,6 @@ install_terraform_docs() { } if [ "${INSTALL_TERRAFORM_DOCS}" = "true" ]; then - TERRAFORM_DOCS_VERSION="latest" terraform_docs_url='https://github.com/terraform-docs/terraform-docs' find_version_from_git_tags TERRAFORM_DOCS_VERSION $terraform_docs_url tfdocs_filename="terraform-docs-v${TERRAFORM_DOCS_VERSION}-linux-${architecture}.tar.gz" diff --git a/test/terraform/install_terraform_docs_version.sh b/test/terraform/install_terraform_docs_version.sh new file mode 100644 index 000000000..4a747f82e --- /dev/null +++ b/test/terraform/install_terraform_docs_version.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +# Check to make sure the user is vscode +check "user is vscode" whoami | grep vscode + +# Terraform Docs specific tests +check "terraform-docs" terraform-docs --version + +# Verify the pinned version was installed +check "terraform-docs version is pinned to 0.20.0" bash -c "terraform-docs --version | grep 'v0.20.0'" + +# Report result +reportResults diff --git a/test/terraform/scenarios.json b/test/terraform/scenarios.json index 796efbd3e..de897b5e9 100644 --- a/test/terraform/scenarios.json +++ b/test/terraform/scenarios.json @@ -79,6 +79,15 @@ } } }, + "install_terraform_docs_version": { + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "features": { + "terraform": { + "installTerraformDocs": true, + "terraformDocsVersion": "0.20.0" + } + } + }, "terraform_docs_fallback_test": { "image": "mcr.microsoft.com/devcontainers/base:jammy", "features": { From c6f2fbd033181b346af0cb039ad8219cd8e0f41c Mon Sep 17 00:00:00 2001 From: Kaniska Date: Fri, 7 Aug 2026 16:31:49 +0530 Subject: [PATCH 78/78] Use GitHub App token instead of PAT in update workflows (#1702) --- .../update-aws-cli-completer-scripts.yml | 18 ++++++++++++++---- .github/workflows/update-documentation.yml | 18 ++++++++++++++---- .../workflows/update-dotnet-install-script.yml | 18 ++++++++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-aws-cli-completer-scripts.yml b/.github/workflows/update-aws-cli-completer-scripts.yml index fde3a29fc..ea6090fe9 100644 --- a/.github/workflows/update-aws-cli-completer-scripts.yml +++ b/.github/workflows/update-aws-cli-completer-scripts.yml @@ -9,10 +9,20 @@ jobs: runs-on: ubuntu-latest environment: documentation # grants access to secrets.PAT, for creating pull requests permissions: - contents: write - pull-requests: write + contents: read steps: - - uses: actions/checkout@v7 + - name: Generate a token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.DEVCONTAINERS_REPO_AUTOMATION_ID }} + private-key: ${{ secrets.DEVCONTAINERS_REPO_AUTOMATION_PRIVATE_KEY }} + + - name: Checkout + id: checkout + uses: actions/checkout@v7 + with: + token: ${{ steps.app-token.outputs.token }} - name: Run fetch-latest-completer-scripts.sh run: src/aws-cli/scripts/fetch-latest-completer-scripts.sh @@ -20,7 +30,7 @@ jobs: - name: Create a PR for completer scripts id: push_image_info env: - GITHUB_TOKEN: ${{ secrets.PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: | set -e echo "Start." diff --git a/.github/workflows/update-documentation.yml b/.github/workflows/update-documentation.yml index 50a643fd7..c766a6871 100644 --- a/.github/workflows/update-documentation.yml +++ b/.github/workflows/update-documentation.yml @@ -10,11 +10,21 @@ jobs: runs-on: ubuntu-latest environment: documentation permissions: - contents: write - pull-requests: write + contents: read if: "github.ref == 'refs/heads/main'" steps: - - uses: actions/checkout@v7 + - name: Generate a token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.DEVCONTAINERS_REPO_AUTOMATION_ID }} + private-key: ${{ secrets.DEVCONTAINERS_REPO_AUTOMATION_PRIVATE_KEY }} + + - name: Checkout + id: checkout + uses: actions/checkout@v7 + with: + token: ${{ steps.app-token.outputs.token }} - name: Generate Documentation uses: devcontainers/action@v1 @@ -25,7 +35,7 @@ jobs: - name: Create a PR for Documentation id: push_image_info env: - GITHUB_TOKEN: ${{ secrets.PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: | set -e echo "Start." diff --git a/.github/workflows/update-dotnet-install-script.yml b/.github/workflows/update-dotnet-install-script.yml index 16f737ff2..fd2161d27 100644 --- a/.github/workflows/update-dotnet-install-script.yml +++ b/.github/workflows/update-dotnet-install-script.yml @@ -9,10 +9,20 @@ jobs: runs-on: ubuntu-latest environment: documentation # grants access to secrets.PAT, for creating pull requests permissions: - contents: write - pull-requests: write + contents: read steps: - - uses: actions/checkout@v7 + - name: Generate a token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.DEVCONTAINERS_REPO_AUTOMATION_ID }} + private-key: ${{ secrets.DEVCONTAINERS_REPO_AUTOMATION_PRIVATE_KEY }} + + - name: Checkout + id: checkout + uses: actions/checkout@v7 + with: + token: ${{ steps.app-token.outputs.token }} - name: Run fetch-latest-dotnet-install.sh run: src/dotnet/scripts/fetch-latest-dotnet-install.sh @@ -20,7 +30,7 @@ jobs: - name: Create a PR for dotnet-install.sh id: push_image_info env: - GITHUB_TOKEN: ${{ secrets.PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: | set -e echo "Start."