From 2237731474ace274b1756e63937a958131cf719e Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Fri, 14 Apr 2023 13:48:25 -0700 Subject: [PATCH 001/361] Automated documentation update (#521) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/aws-cli/README.md | 6 ++++++ src/azure-cli/README.md | 6 ++++++ src/docker-in-docker/README.md | 6 ++++++ src/docker-outside-of-docker/README.md | 6 ++++++ src/dotnet/README.md | 6 ++++++ src/go/README.md | 6 ++++++ src/java/README.md | 6 ++++++ src/node/README.md | 6 ++++++ src/php/README.md | 9 +++++++++ src/python/README.md | 7 +++++++ src/ruby/README.md | 6 ++++++ src/rust/README.md | 10 ++++++++++ src/terraform/README.md | 7 +++++++ 13 files changed, 87 insertions(+) diff --git a/src/aws-cli/README.md b/src/aws-cli/README.md index 40cc4ae98..e45cb3f4c 100644 --- a/src/aws-cli/README.md +++ b/src/aws-cli/README.md @@ -17,6 +17,12 @@ Installs the AWS CLI along with needed dependencies. Useful for base Dockerfiles |-----|-----|-----|-----| | version | Select or enter an AWS CLI version. | string | latest | +## Customizations + +### VS Code Extensions + +- `AmazonWebServices.aws-toolkit-vscode` + Available versions of the AWS CLI can be found here: https://github.com/aws/aws-cli/blob/v2/CHANGELOG.rst. ## OS Support diff --git a/src/azure-cli/README.md b/src/azure-cli/README.md index cb792d120..4a9de7b96 100644 --- a/src/azure-cli/README.md +++ b/src/azure-cli/README.md @@ -19,6 +19,12 @@ Installs the Azure CLI along with needed dependencies. Useful for base Dockerfil | extensions | Optional comma separated list of Azure CLI extensions to install in profile. | string | - | | installBicep | Optionally install Azure Bicep | boolean | false | +## Customizations + +### VS Code Extensions + +- `ms-vscode.azurecli` + ## OS Support diff --git a/src/docker-in-docker/README.md b/src/docker-in-docker/README.md index 9a22c2c4e..d519a48fb 100644 --- a/src/docker-in-docker/README.md +++ b/src/docker-in-docker/README.md @@ -22,6 +22,12 @@ Create child containers *inside* a container, independent from the host's docker | 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 | +## Customizations + +### VS Code Extensions + +- `ms-azuretools.vscode-docker` + ## Limitations This docker-in-docker Dev Container Feature is roughly based on the [official docker-in-docker wrapper script](https://github.com/moby/moby/blob/master/hack/dind) that is part of the [Moby project](https://mobyproject.org/). With this in mind: diff --git a/src/docker-outside-of-docker/README.md b/src/docker-outside-of-docker/README.md index f7f411a72..7b558a971 100644 --- a/src/docker-outside-of-docker/README.md +++ b/src/docker-outside-of-docker/README.md @@ -22,6 +22,12 @@ 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) | string | v2 | | installDockerBuildx | Install Docker Buildx | boolean | true | +## Customizations + +### VS Code Extensions + +- `ms-azuretools.vscode-docker` + ## Limitations - 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. diff --git a/src/dotnet/README.md b/src/dotnet/README.md index c13c998fd..83c42494f 100644 --- a/src/dotnet/README.md +++ b/src/dotnet/README.md @@ -19,6 +19,12 @@ Installs the .NET CLI. Provides option of installing sdk or runtime, and option | runtimeOnly | Install just the dotnet runtime if true, and sdk if false. | boolean | false | | installUsingApt | If true, it installs using apt instead of the release URL | boolean | true | +## Customizations + +### VS Code Extensions + +- `ms-dotnettools.csharp` + ## OS Support diff --git a/src/go/README.md b/src/go/README.md index e7a692443..be035ee85 100644 --- a/src/go/README.md +++ b/src/go/README.md @@ -18,6 +18,12 @@ Installs Go and common Go utilities. Auto-detects latest version and installs ne | version | Select or enter a Go version to install | string | latest | | golangciLintVersion | Version of golangci-lint to install | string | latest | +## Customizations + +### VS Code Extensions + +- `golang.Go` + ## OS Support diff --git a/src/java/README.md b/src/java/README.md index e74cd5a10..1ffd6188b 100644 --- a/src/java/README.md +++ b/src/java/README.md @@ -24,6 +24,12 @@ Installs Java, SDKMAN! (if not installed), and needed dependencies. | installAnt | Install Ant, a software tool for automating software build processes | boolean | false | | antVersion | Select or enter an Ant version | string | latest | +## Customizations + +### VS Code Extensions + +- `vscjava.vscode-java-pack` + ## License For the Java Feature from this repository, see [NOTICE.txt](https://github.com/devcontainers/features/tree/main/src/java/NOTICE.txt) for licensing information on JDK distributions. diff --git a/src/node/README.md b/src/node/README.md index 877ea5877..7a4909600 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -20,6 +20,12 @@ Installs Node.js, nvm, yarn, pnpm, and needed dependencies. | nvmInstallPath | The path where NVM will be installed. | string | /usr/local/share/nvm | | nvmVersion | Version of NVM to install. | string | 0.39.2 | +## Customizations + +### VS Code Extensions + +- `dbaeumer.vscode-eslint` + ## Using nvm from postCreateCommand or another lifecycle command Certain operations like `postCreateCommand` run non-interactive, non-login shells. Unfortunately, `nvm` is really particular that it needs to be "sourced" before it is used, which can only happen automatically with interactive and/or login shells. Fortunately, this is easy to work around: diff --git a/src/php/README.md b/src/php/README.md index 3a9be7a98..14f4a1797 100644 --- a/src/php/README.md +++ b/src/php/README.md @@ -18,6 +18,15 @@ | version | Select or enter a PHP version | string | latest | | installComposer | Install PHP Composer? | boolean | true | +## Customizations + +### VS Code Extensions + +- `xdebug.php-debug` +- `bmewburn.vscode-intelephense-client` +- `xdebug.php-pack` +- `devsense.phptools-vscode` + ## OS Support diff --git a/src/python/README.md b/src/python/README.md index 05742910a..807d89eb1 100644 --- a/src/python/README.md +++ b/src/python/README.md @@ -22,6 +22,13 @@ Installs the provided version of Python, as well as PIPX, and other common Pytho | installJupyterlab | Install JupyterLab, a web-based interactive development environment for notebooks | boolean | false | | configureJupyterlabAllowOrigin | Configure JupyterLab to accept HTTP requests from the specified origin | string | - | +## Customizations + +### VS Code Extensions + +- `ms-python.python` +- `ms-python.vscode-pylance` + ## OS Support diff --git a/src/ruby/README.md b/src/ruby/README.md index 8a0ff877d..8e8ebe2c3 100644 --- a/src/ruby/README.md +++ b/src/ruby/README.md @@ -17,6 +17,12 @@ Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies. |-----|-----|-----|-----| | version | Select or enter a Ruby version to install | string | latest | +## Customizations + +### VS Code Extensions + +- `rebornix.Ruby` + ## OS Support diff --git a/src/rust/README.md b/src/rust/README.md index 372b56f97..f72b56f82 100644 --- a/src/rust/README.md +++ b/src/rust/README.md @@ -18,6 +18,16 @@ Installs Rust, common Rust utilities, and their required dependencies | version | Select or enter a version of Rust to install. | string | latest | | profile | Select a rustup install profile. | string | minimal | +## Customizations + +### VS Code Extensions + +- `vadimcn.vscode-lldb` +- `mutantdino.resourcemonitor` +- `rust-lang.rust-analyzer` +- `tamasfe.even-better-toml` +- `serayuzgur.crates` + ## OS Support diff --git a/src/terraform/README.md b/src/terraform/README.md index e75da9b20..e93058718 100644 --- a/src/terraform/README.md +++ b/src/terraform/README.md @@ -23,6 +23,13 @@ Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects la | installTerraformDocs | Install terraform-docs, a utility to generate documentation from Terraform modules | boolean | false | | httpProxy | Connect to a keyserver using a proxy by configuring this option | string | - | +## Customizations + +### VS Code Extensions + +- `HashiCorp.terraform` +- `ms-azuretools.vscode-azureterraform` + ## OS Support From fad03b19d48466bb0eec41ccbf4cc456a5c4dea7 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Tue, 18 Apr 2023 14:37:51 -0700 Subject: [PATCH 002/361] Oryx - Switch to .NET 7 (#522) * Oryx - Switch to .NET 7 * fix test ; add test * fix test - back compat --- src/oryx/devcontainer-feature.json | 5 ++- src/oryx/install.sh | 16 +++++--- test/oryx/install_prev_dotnet_and_oryx.sh | 47 +++++++++++++++++++++++ test/oryx/scenarios.json | 10 +++++ 4 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 test/oryx/install_prev_dotnet_and_oryx.sh diff --git a/src/oryx/devcontainer-feature.json b/src/oryx/devcontainer-feature.json index 9a6bd25c2..66a528ec5 100644 --- a/src/oryx/devcontainer-feature.json +++ b/src/oryx/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "oryx", - "version": "1.0.13", + "version": "1.1.0", "name": "Oryx", "description": "Installs the oryx CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/oryx", @@ -14,6 +14,7 @@ "PATH": "/usr/local/oryx:${PATH}" }, "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/devcontainers/features/dotnet" ] } diff --git a/src/oryx/install.sh b/src/oryx/install.sh index a1b64d3e9..cb2e1b6b7 100755 --- a/src/oryx/install.sh +++ b/src/oryx/install.sh @@ -74,7 +74,7 @@ install_dotnet_using_apt() { echo "Attempting to auto-install dotnet..." install_from_microsoft_feed=false apt_get_update - DOTNET_INSTALLATION_PACKAGE="dotnet6" + DOTNET_INSTALLATION_PACKAGE="dotnet7" apt-get -yq install $DOTNET_INSTALLATION_PACKAGE || install_from_microsoft_feed="true" if [ "${install_from_microsoft_feed}" = "true" ]; then @@ -82,7 +82,7 @@ install_dotnet_using_apt() { 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/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list apt-get update -y - DOTNET_INSTALLATION_PACKAGE="dotnet-sdk-6.0" + DOTNET_INSTALLATION_PACKAGE="dotnet-sdk-7.0" DOTNET_SKIP_FIRST_TIME_EXPERIENCE="true" apt-get install -yq $DOTNET_INSTALLATION_PACKAGE fi @@ -132,9 +132,9 @@ if dotnet --version > /dev/null ; then DOTNET_BINARY=$(which dotnet) fi -# Oryx needs to be built with .NET 6 -if [[ "${DOTNET_BINARY}" = "" ]] || [[ "$(dotnet --version)" != *"6"* ]] ; then - echo "'dotnet 6' was not detected. Attempting to install .NET 6 to build oryx." +# Oryx needs to be built with .NET 7 +if [[ "${DOTNET_BINARY}" = "" ]] || [[ "$(dotnet --version)" != *"7"* ]] ; then + echo "'dotnet 7' was not detected. Attempting to install .NET 7 to build oryx." install_dotnet_using_apt if ! dotnet --version > /dev/null ; then @@ -154,7 +154,11 @@ mkdir -p ${ORYX} git clone --depth=1 https://github.com/microsoft/Oryx $GIT_ORYX -$GIT_ORYX/build/buildSln.sh +SOLUTION_FILE_NAME="Oryx.sln" +echo "Building solution '$SOLUTION_FILE_NAME'..." + +cd $GIT_ORYX +${DOTNET_BINARY} build "$SOLUTION_FILE_NAME" -c Debug ${DOTNET_BINARY} publish -property:ValidateExecutableReferencesMatchSelfContained=false -r linux-x64 -o ${BUILD_SCRIPT_GENERATOR} -c Release $GIT_ORYX/src/BuildScriptGeneratorCli/BuildScriptGeneratorCli.csproj ${DOTNET_BINARY} publish -r linux-x64 -o ${BUILD_SCRIPT_GENERATOR} -c Release $GIT_ORYX/src/BuildServer/BuildServer.csproj diff --git a/test/oryx/install_prev_dotnet_and_oryx.sh b/test/oryx/install_prev_dotnet_and_oryx.sh new file mode 100644 index 000000000..175482847 --- /dev/null +++ b/test/oryx/install_prev_dotnet_and_oryx.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +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-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-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 + +# Replicates Oryx's behavior for universal image +mkdir -p /opt/oryx +echo "vso-focal" >> /opt/oryx/.imagetype + +mkdir -p /opt/dotnet/lts +cp -R /usr/local/dotnet/current/dotnet /opt/dotnet/lts +cp -R /usr/local/dotnet/current/LICENSE.txt /opt/dotnet/lts +cp -R /usr/local/dotnet/current/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-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-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 + +# Report result +reportResults diff --git a/test/oryx/scenarios.json b/test/oryx/scenarios.json index bbfc277a9..c61483eb4 100644 --- a/test/oryx/scenarios.json +++ b/test/oryx/scenarios.json @@ -1,5 +1,15 @@ { "install_dotnet_and_oryx": { + "image": "ubuntu:focal", + "features": { + "dotnet": { + "version": "7", + "installUsingApt": "false" + }, + "oryx": {} + } + }, + "install_prev_dotnet_and_oryx": { "image": "ubuntu:focal", "features": { "dotnet": { From f52ff611c55a4e0708edd9c93076603b2704e581 Mon Sep 17 00:00:00 2001 From: Felix Altenberg Date: Fri, 28 Apr 2023 21:38:25 +0100 Subject: [PATCH 003/361] Adds httpProxy option to Python Feature (#532) * Adds httpProxy option to Python Feature This option allows GPG to use a HTTP proxy to fetch keys from remote keyservers * Updating minor version Co-authored-by: Samruddhi Khandale * Fixes minor formatting error --------- Co-authored-by: Samruddhi Khandale --- src/python/devcontainer-feature.json | 7 ++++++- src/python/install.sh | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/python/devcontainer-feature.json b/src/python/devcontainer-feature.json index a954c3511..b4aa21e25 100644 --- a/src/python/devcontainer-feature.json +++ b/src/python/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "python", - "version": "1.0.22", + "version": "1.1.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.", @@ -45,6 +45,11 @@ "type": "string", "default": "", "description": "Configure JupyterLab to accept HTTP requests from the specified origin" + }, + "httpProxy": { + "type": "string", + "default": "", + "description": "Connect to GPG keyservers using a proxy for fetching source code signatures by configuring this option" } }, "containerEnv": { diff --git a/src/python/install.sh b/src/python/install.sh index 053a72aff..0e2b1253c 100755 --- a/src/python/install.sh +++ b/src/python/install.sh @@ -32,6 +32,8 @@ GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com keyserver hkps://keys.openpgp.org keyserver hkp://keyserver.pgp.com" +KEYSERVER_PROXY="${HTTPPROXY:-"${HTTP_PROXY:-""}"}" + set -e # Clean up @@ -84,6 +86,9 @@ receive_gpg_keys() { mkdir -p "$(dirname \"$2\")" keyring_args="--no-default-keyring --keyring $2" fi + if [ ! -z "${KEYSERVER_PROXY}" ]; then + keyring_args="${keyring_args} --keyserver-options http-proxy=${KEYSERVER_PROXY}" + fi # Use a temporary location for gpg keys to avoid polluting image export GNUPGHOME="/tmp/tmp-gnupg" From e2b3b48bc52be49b4e0063012e9ccdf0bf7672f4 Mon Sep 17 00:00:00 2001 From: Raymond <44623277+raymond-chetty@users.noreply.github.com> Date: Mon, 1 May 2023 08:37:43 -0700 Subject: [PATCH 004/361] Update devcontainer-feature.json (#535) #531 Modify proposed UID & GID so as not to conflict with base image. --- src/common-utils/devcontainer-feature.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 71b6e6687..8a532559f 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -40,7 +40,7 @@ "userUid": { "type": "string", "proposals": [ - "1000", + "1001", "automatic" ], "default": "automatic", @@ -49,7 +49,7 @@ "userGid": { "type": "string", "proposals": [ - "1000", + "1001", "automatic" ], "default": "automatic", From b16ff1efb7b092c73ab1e13c3d016d1ab7a9e4b6 Mon Sep 17 00:00:00 2001 From: Raymond <44623277+raymond-chetty@users.noreply.github.com> Date: Mon, 1 May 2023 08:38:01 -0700 Subject: [PATCH 005/361] Update devcontainer-feature.json (#539) #535 update the patch version to 2.0.9 --- 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 8a532559f..9ae905b62 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.0.8", + "version": "2.0.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.", From 4312340798680a2b148847b124fcf737e5839c2e Mon Sep 17 00:00:00 2001 From: "M. R" <2947595+m-roberts@users.noreply.github.com> Date: Tue, 2 May 2023 20:00:45 +0100 Subject: [PATCH 006/361] Add support for skipping `docker-compose` v1 operations during Docker feature install script (#533) * Add support for skipping docker-compose operations * Move changes from auto-generated files to templates and bump version * Add test * Add test conditions * Add test conditions for docker-outside-of-docker * install.sh set -x for debugging * set -x * Move compose-switch check in docker-outside-of-docker test * Put into correct test * Test for correct path --- .../devcontainer-feature.json | 5 +- src/docker-in-docker/install.sh | 103 +++++++++--------- .../devcontainer-feature.json | 5 +- src/docker-outside-of-docker/install.sh | 71 ++++++------ test/docker-in-docker/docker_build.sh | 3 + .../docker_build_no_compose.sh | 16 +++ test/docker-in-docker/scenarios.json | 10 ++ test/docker-outside-of-docker/docker_build.sh | 2 + .../docker_build_no_compose.sh | 15 +++ .../docker_dash_compose_v2.sh | 2 + test/docker-outside-of-docker/scenarios.json | 9 ++ 11 files changed, 153 insertions(+), 88 deletions(-) mode change 100644 => 100755 test/docker-in-docker/docker_build.sh create mode 100755 test/docker-in-docker/docker_build_no_compose.sh mode change 100644 => 100755 test/docker-outside-of-docker/docker_build.sh create mode 100755 test/docker-outside-of-docker/docker_build_no_compose.sh diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 798b8e87a..1555cd6e7 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.1.0", + "version": "2.2.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.", @@ -23,11 +23,12 @@ "dockerDashComposeVersion": { "type": "string", "enum": [ + "none", "v1", "v2" ], "default": "v1", - "description": "Default version of Docker Compose (v1 or v2)" + "description": "Default version of Docker Compose (v1 or v2 or none)" }, "azureDnsAutoDetection": { "type": "boolean", diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index 770519902..9ad807e3c 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -10,7 +10,7 @@ DOCKER_VERSION="${VERSION:-"latest"}" # The Docker/Moby Engine + CLI should match in version USE_MOBY="${MOBY:-"true"}" -DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v1"}" # v1 or v2 +DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v1"}" # v1 or v2 or none AZURE_DNS_AUTO_DETECTION="${AZUREDNSAUTODETECTION:-"true"}" DOCKER_DEFAULT_ADDRESS_POOL="${DOCKERDEFAULTADDRESSPOOL}" USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" @@ -242,58 +242,61 @@ fi echo "Finished installing docker / moby!" -# Install Docker Compose if not already installed and is on a supported architecture -if type docker-compose > /dev/null 2>&1; then - echo "Docker Compose v1 already installed." -else - target_compose_arch="${architecture}" - if [ "${target_compose_arch}" = "amd64" ]; then - target_compose_arch="x86_64" - fi - if [ "${target_compose_arch}" != "x86_64" ]; then - # Use pip to get a version that runs on this architecture - check_packages python3-minimal python3-pip libffi-dev python3-venv - export PIPX_HOME=/usr/local/pipx - mkdir -p ${PIPX_HOME} - export PIPX_BIN_DIR=/usr/local/bin - export PYTHONUSERBASE=/tmp/pip-tmp - export PIP_CACHE_DIR=/tmp/pip-tmp/cache - pipx_bin=pipx - if ! type pipx > /dev/null 2>&1; then - pip3 install --disable-pip-version-check --no-cache-dir --user pipx - pipx_bin=/tmp/pip-tmp/bin/pipx - fi - ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose - rm -rf /tmp/pip-tmp +# If 'docker-compose' command is to be included +if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then + # Install Docker Compose if not already installed and is on a supported architecture + if type docker-compose > /dev/null 2>&1; then + echo "Docker Compose v1 already installed." else - compose_v1_version="1" - find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/" - echo "(*) Installing docker-compose ${compose_v1_version}..." - curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose + target_compose_arch="${architecture}" + if [ "${target_compose_arch}" = "amd64" ]; then + target_compose_arch="x86_64" + fi + if [ "${target_compose_arch}" != "x86_64" ]; then + # Use pip to get a version that runs on this architecture + check_packages python3-minimal python3-pip libffi-dev python3-venv + export PIPX_HOME=/usr/local/pipx + mkdir -p ${PIPX_HOME} + export PIPX_BIN_DIR=/usr/local/bin + export PYTHONUSERBASE=/tmp/pip-tmp + export PIP_CACHE_DIR=/tmp/pip-tmp/cache + pipx_bin=pipx + if ! type pipx > /dev/null 2>&1; then + pip3 install --disable-pip-version-check --no-cache-dir --user pipx + pipx_bin=/tmp/pip-tmp/bin/pipx + fi + ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose + rm -rf /tmp/pip-tmp + else + compose_v1_version="1" + find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/" + echo "(*) Installing docker-compose ${compose_v1_version}..." + curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + fi fi -fi -# Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation -current_v1_compose_path="$(which docker-compose)" -target_v1_compose_path="$(dirname "${current_v1_compose_path}")/docker-compose-v1" -if ! type compose-switch > /dev/null 2>&1; then - echo "(*) Installing compose-switch..." - compose_switch_version="latest" - find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch" - curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch - 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) - mv "${current_v1_compose_path}" "${target_v1_compose_path}" - update-alternatives --install /usr/local/bin/docker-compose docker-compose /usr/local/bin/compose-switch 99 - update-alternatives --install /usr/local/bin/docker-compose docker-compose "${target_v1_compose_path}" 1 -fi -if [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then - update-alternatives --set docker-compose "${target_v1_compose_path}" -else - update-alternatives --set docker-compose /usr/local/bin/compose-switch + # Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation + current_v1_compose_path="$(which docker-compose)" + target_v1_compose_path="$(dirname "${current_v1_compose_path}")/docker-compose-v1" + if ! type compose-switch > /dev/null 2>&1; then + echo "(*) Installing compose-switch..." + compose_switch_version="latest" + find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch" + curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch + 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) + mv "${current_v1_compose_path}" "${target_v1_compose_path}" + update-alternatives --install /usr/local/bin/docker-compose docker-compose /usr/local/bin/compose-switch 99 + update-alternatives --install /usr/local/bin/docker-compose docker-compose "${target_v1_compose_path}" 1 + fi + if [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then + update-alternatives --set docker-compose "${target_v1_compose_path}" + else + update-alternatives --set docker-compose /usr/local/bin/compose-switch + fi fi # If init file already exists, exit diff --git a/src/docker-outside-of-docker/devcontainer-feature.json b/src/docker-outside-of-docker/devcontainer-feature.json index 2b75aa4f3..d3123fabe 100644 --- a/src/docker-outside-of-docker/devcontainer-feature.json +++ b/src/docker-outside-of-docker/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "docker-outside-of-docker", - "version": "1.2.1", + "version": "1.3.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.", @@ -23,11 +23,12 @@ "dockerDashComposeVersion": { "type": "string", "enum": [ + "none", "v1", "v2" ], "default": "v2", - "description": "Compose version to use for docker-compose (v1 or v2)" + "description": "Compose version to use for docker-compose (v1 or v2 or none)" }, "installDockerBuildx": { "type": "boolean", diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index f32db2f4a..7bf2138b4 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -9,7 +9,7 @@ DOCKER_VERSION="${VERSION:-"latest"}" USE_MOBY="${MOBY:-"true"}" -DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v2"}" # v1 or v2 +DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v1"}" # v1 or v2 or none ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}" SOURCE_SOCKET="${SOURCE_SOCKET:-"/var/run/docker-host.sock"}" @@ -216,43 +216,46 @@ else unset buildx buildx_path fi -# Install Docker Compose if not already installed and is on a supported architecture -if type docker-compose > /dev/null 2>&1; then - echo "Docker Compose already installed." -elif [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then - TARGET_COMPOSE_ARCH="$(uname -m)" - if [ "${TARGET_COMPOSE_ARCH}" = "amd64" ]; then - TARGET_COMPOSE_ARCH="x86_64" - fi - if [ "${TARGET_COMPOSE_ARCH}" != "x86_64" ]; then - # Use pip to get a version that runs on this architecture - check_packages python3-minimal python3-pip libffi-dev python3-venv - export PIPX_HOME=/usr/local/pipx - mkdir -p ${PIPX_HOME} - export PIPX_BIN_DIR=/usr/local/bin - export PYTHONUSERBASE=/tmp/pip-tmp - export PIP_CACHE_DIR=/tmp/pip-tmp/cache - pipx_bin=pipx - if ! type pipx > /dev/null 2>&1; then - pip3 install --disable-pip-version-check --no-cache-dir --user pipx - pipx_bin=/tmp/pip-tmp/bin/pipx +# If 'docker-compose' command is to be included +if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then + # Install Docker Compose if not already installed and is on a supported architecture + if type docker-compose > /dev/null 2>&1; then + echo "Docker Compose already installed." + elif [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then + TARGET_COMPOSE_ARCH="$(uname -m)" + if [ "${TARGET_COMPOSE_ARCH}" = "amd64" ]; then + TARGET_COMPOSE_ARCH="x86_64" + fi + if [ "${TARGET_COMPOSE_ARCH}" != "x86_64" ]; then + # Use pip to get a version that runs on this architecture + check_packages python3-minimal python3-pip libffi-dev python3-venv + export PIPX_HOME=/usr/local/pipx + mkdir -p ${PIPX_HOME} + export PIPX_BIN_DIR=/usr/local/bin + export PYTHONUSERBASE=/tmp/pip-tmp + export PIP_CACHE_DIR=/tmp/pip-tmp/cache + pipx_bin=pipx + if ! type pipx > /dev/null 2>&1; then + pip3 install --disable-pip-version-check --no-cache-dir --user pipx + pipx_bin=/tmp/pip-tmp/bin/pipx + fi + ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose + rm -rf /tmp/pip-tmp + else + compose_v1_version="1" + find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/" + echo "(*) Installing docker-compose ${compose_v1_version}..." + curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose fi - ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose - rm -rf /tmp/pip-tmp else - compose_v1_version="1" - find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/" - echo "(*) Installing docker-compose ${compose_v1_version}..." - curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose + echo "(*) Installing compose-switch as docker-compose..." + compose_switch_version="latest" + find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch" + curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose + # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11 fi -else - echo "(*) Installing compose-switch as docker-compose..." - compose_switch_version="latest" - find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch" - curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose - # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11 fi # Setup a docker group in the event the docker socket's group is not root diff --git a/test/docker-in-docker/docker_build.sh b/test/docker-in-docker/docker_build.sh old mode 100644 new mode 100755 index 742b222fb..d51e7d644 --- a/test/docker-in-docker/docker_build.sh +++ b/test/docker-in-docker/docker_build.sh @@ -9,5 +9,8 @@ source dev-container-features-test-lib 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 ]]" + # Report result reportResults diff --git a/test/docker-in-docker/docker_build_no_compose.sh b/test/docker-in-docker/docker_build_no_compose.sh new file mode 100755 index 000000000..e913be321 --- /dev/null +++ b/test/docker-in-docker/docker_build_no_compose.sh @@ -0,0 +1,16 @@ +#!/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 "not installing compose skips docker-compose v1 install" bash -c "! type docker-compose" +check "not installing compose skips compose-switch" 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 5123d0916..9c44478f1 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -58,5 +58,15 @@ } }, "remoteUser": "node" + }, + "docker_build_no_compose": { + "image": "mcr.microsoft.com/devcontainers/javascript-node:0-18", + "features": { + "docker-in-docker": { + "dockerDashComposeVersion": "none" + } + }, + "remoteUser": "node" } + } diff --git a/test/docker-outside-of-docker/docker_build.sh b/test/docker-outside-of-docker/docker_build.sh old mode 100644 new mode 100755 index 742b222fb..056a76425 --- a/test/docker-outside-of-docker/docker_build.sh +++ b/test/docker-outside-of-docker/docker_build.sh @@ -9,5 +9,7 @@ source dev-container-features-test-lib check "docker-buildx" docker buildx version check "docker-build" docker build ./ +check "installs docker-compose v1 install" bash -c "type docker-compose" + # Report result reportResults diff --git a/test/docker-outside-of-docker/docker_build_no_compose.sh b/test/docker-outside-of-docker/docker_build_no_compose.sh new file mode 100755 index 000000000..477bf2149 --- /dev/null +++ b/test/docker-outside-of-docker/docker_build_no_compose.sh @@ -0,0 +1,15 @@ +#!/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 "not installing compose skips docker-compose v1 install" bash -c "! type docker-compose" + +# Report result +reportResults diff --git a/test/docker-outside-of-docker/docker_dash_compose_v2.sh b/test/docker-outside-of-docker/docker_dash_compose_v2.sh index 8f2a104f5..b24238f0d 100755 --- a/test/docker-outside-of-docker/docker_dash_compose_v2.sh +++ b/test/docker-outside-of-docker/docker_dash_compose_v2.sh @@ -9,5 +9,7 @@ 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 ]]" + # Report result reportResults diff --git a/test/docker-outside-of-docker/scenarios.json b/test/docker-outside-of-docker/scenarios.json index be63470c5..3b82c6cc5 100644 --- a/test/docker-outside-of-docker/scenarios.json +++ b/test/docker-outside-of-docker/scenarios.json @@ -115,5 +115,14 @@ } }, "containerUser": "vscode" + }, + "docker_build_no_compose": { + "image": "mcr.microsoft.com/devcontainers/javascript-node:0-18", + "features": { + "docker-in-docker": { + "dockerDashComposeVersion": "none" + } + }, + "remoteUser": "node" } } From 4420cd5d27a551abbaa622bcde71dedb39617283 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Tue, 9 May 2023 13:52:06 -0700 Subject: [PATCH 007/361] Automated documentation update (#543) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/docker-in-docker/README.md | 2 +- src/docker-outside-of-docker/README.md | 2 +- src/python/README.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/docker-in-docker/README.md b/src/docker-in-docker/README.md index d519a48fb..bfca77357 100644 --- a/src/docker-in-docker/README.md +++ b/src/docker-in-docker/README.md @@ -17,7 +17,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 | -| dockerDashComposeVersion | Default version of Docker Compose (v1 or v2) | string | v1 | +| dockerDashComposeVersion | Default version of Docker Compose (v1 or v2 or none) | string | v1 | | 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-outside-of-docker/README.md b/src/docker-outside-of-docker/README.md index 7b558a971..94c891564 100644 --- a/src/docker-outside-of-docker/README.md +++ b/src/docker-outside-of-docker/README.md @@ -19,7 +19,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 | -| dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2) | string | v2 | +| dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none) | string | v2 | | installDockerBuildx | Install Docker Buildx | boolean | true | ## Customizations diff --git a/src/python/README.md b/src/python/README.md index 807d89eb1..2dd092ae7 100644 --- a/src/python/README.md +++ b/src/python/README.md @@ -21,6 +21,7 @@ Installs the provided version of Python, as well as PIPX, and other common Pytho | installPath | The path where python will be installed. | string | /usr/local/python | | installJupyterlab | Install JupyterLab, a web-based interactive development environment for notebooks | boolean | false | | configureJupyterlabAllowOrigin | Configure JupyterLab to accept HTTP requests from the specified origin | string | - | +| httpProxy | Connect to GPG keyservers using a proxy for fetching source code signatures by configuring this option | string | - | ## Customizations From 1869e5931cfe0517f75d58cb70863a6b4874c487 Mon Sep 17 00:00:00 2001 From: "Stephen A. Imhoff" Date: Wed, 10 May 2023 10:04:16 -0700 Subject: [PATCH 008/361] Create .config directory in common-utils (#547) * Add test case for mounted subdirectory. * Correct some trailing whitespace. * Correct variable name to better reflect status as user home path. * Add config directory creation. * Remove extra debug line. * Bump feature version. * Mount host user home directory as "subdirectory" * Correct test naming, run test in explicit bash shell. --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 53 +++++++++++++--------- test/common-utils/config-subdirectory.sh | 13 ++++++ test/common-utils/scenarios.json | 10 ++++ 4 files changed, 55 insertions(+), 23 deletions(-) create mode 100755 test/common-utils/config-subdirectory.sh diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 9ae905b62..aadc1bc27 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.0.9", + "version": "2.0.10", "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 2a8cd282e..144478ee1 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -68,7 +68,7 @@ install_debian_packages() { manpages \ manpages-dev \ init-system-helpers" - + # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then # Bring in variables from /etc/os-release like VERSION_CODENAME @@ -78,7 +78,7 @@ install_debian_packages() { sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list @@ -133,13 +133,13 @@ install_debian_packages() { # Ensure at least the en_US.UTF-8 UTF-8 locale is available = common need for both applications and things like the agnoster ZSH theme. if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then - echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen locale-gen LOCALE_ALREADY_SET="true" fi # Clean up - apt-get -y clean + apt-get -y clean rm -rf /var/lib/apt/lists/* } @@ -254,7 +254,7 @@ install_alpine_packages() { # 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 + else apk add --no-cache mandoc man-pages fi @@ -324,7 +324,7 @@ fi if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then if [ "${_REMOTE_USER}" != "root" ]; then USERNAME="${_REMOTE_USER}" - else + else USERNAME="" POSSIBLE_USERS=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do @@ -346,12 +346,12 @@ fi group_name="${USERNAME}" if id -u ${USERNAME} > /dev/null 2>&1; then # User exists, update if needed - if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then group_name="$(id -gn $USERNAME)" groupmod --gid $USER_GID ${group_name} usermod --gid $USER_GID $USERNAME fi - if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then usermod --uid $USER_UID $USERNAME fi else @@ -361,7 +361,7 @@ else else groupadd --gid $USER_GID $USERNAME fi - if [ "${USER_UID}" = "automatic" ]; then + if [ "${USER_UID}" = "automatic" ]; then useradd -s /bin/bash --gid $USERNAME -m $USERNAME else useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME @@ -379,13 +379,13 @@ fi # ** Shell customization section ** # ********************************* -if [ "${USERNAME}" = "root" ]; then - user_rc_path="/root" +if [ "${USERNAME}" = "root" ]; then + user_home="/root" else - user_rc_path="/home/${USERNAME}" - if [ ! -d "${user_rc_path}" ]; then - mkdir -p "${user_rc_path}" - chown ${USERNAME}:${group_name} "${user_rc_path}" + user_home="/home/${USERNAME}" + if [ ! -d "${user_home}" ]; then + mkdir -p "${user_home}" + chown ${USERNAME}:${group_name} "${user_home}" fi fi @@ -393,9 +393,9 @@ fi possible_rc_files=( ".bashrc" ".profile" ".zshrc" ) for rc_file in "${possible_rc_files[@]}"; do if [ -f "/etc/skel/${rc_file}" ]; then - if [ ! -e "${user_rc_path}/${rc_file}" ] || [ ! -s "${user_rc_path}/${rc_file}" ]; then - cp "/etc/skel/${rc_file}" "${user_rc_path}/${rc_file}" - chown ${USERNAME}:${group_name} "${user_rc_path}/${rc_file}" + if [ ! -e "${user_home}/${rc_file}" ] || [ ! -s "${user_home}/${rc_file}" ]; then + cp "/etc/skel/${rc_file}" "${user_home}/${rc_file}" + chown ${USERNAME}:${group_name} "${user_home}/${rc_file}" fi fi done @@ -416,10 +416,10 @@ if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then ;; esac cat "${FEATURE_DIR}/scripts/rc_snippet.sh" >> ${global_rc_path} - cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "${user_rc_path}/.bashrc" + cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "${user_home}/.bashrc" if [ "${USERNAME}" != "root" ]; then cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "/root/.bashrc" - chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" + chown ${USERNAME}:${group_name} "${user_home}/.bashrc" fi RC_SNIPPET_ALREADY_ADDED="true" fi @@ -442,10 +442,10 @@ if [ "${INSTALL_ZSH}" = "true" ]; then # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. - oh_my_install_dir="${user_rc_path}/.oh-my-zsh" + oh_my_install_dir="${user_home}/.oh-my-zsh" if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MY_ZSH}" = "true" ]; then template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" - user_rc_file="${user_rc_path}/.zshrc" + user_rc_file="${user_home}/.zshrc" umask g-w,o-w mkdir -p ${oh_my_install_dir} git clone --depth=1 \ @@ -474,6 +474,15 @@ if [ "${INSTALL_ZSH}" = "true" ]; then fi fi +# ********************************* +# ** Ensure config directory ** +# ********************************* +user_config_dir="${user_home}/.config" +if [ ! -d "${user_config_dir}" ]; then + mkdir -p "${user_config_dir}" + chown ${USERNAME}:${group_name} "${user_config_dir}" +fi + # **************************** # ** Utilities and commands ** # **************************** diff --git a/test/common-utils/config-subdirectory.sh b/test/common-utils/config-subdirectory.sh new file mode 100755 index 000000000..ba67fb68f --- /dev/null +++ b/test/common-utils/config-subdirectory.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "owned-config-sub-directory" bash -c "ls -ld ~/.config/subdirectory | awk '{print $3}' | grep 'devcontainer'" +check "owned-config-directory" bash -c "ls -ld ~/.config | awk '{print $3}' | grep 'devcontainer'" + +# Report result +reportResults diff --git a/test/common-utils/scenarios.json b/test/common-utils/scenarios.json index 43d11c9a1..21cb9408a 100644 --- a/test/common-utils/scenarios.json +++ b/test/common-utils/scenarios.json @@ -115,6 +115,16 @@ } } }, + "config-subdirectory": { + "image": "alpine", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + }, + "mounts": [ + "source=${localEnv:HOME},target=/home/devcontainer/.config/subdirectory,type=bind,readonly" + ] + }, "alpine-3-14": { "image": "alpine:3.14", "remoteUser": "devcontainer", From b48d8700ffe5c0b363c9e8706ba2c1601a2bb94f Mon Sep 17 00:00:00 2001 From: Raymond <44623277+raymond-chetty@users.noreply.github.com> Date: Mon, 22 May 2023 12:23:23 -0700 Subject: [PATCH 009/361] Update NOTES.md (#534) * Update NOTES.md #531: Quick document update about UID & GID using 1000 in devcontainer base images. * Update src/common-utils/NOTES.md @samruddhikhandale The common-utils Feature is used by most of the dev container images, along with the base image. Can we update the text? Co-authored-by: Samruddhi Khandale * Update src/common-utils/NOTES.md @samruddhikhandale Co-authored-by: Samruddhi Khandale --------- Co-authored-by: Samruddhi Khandale --- src/common-utils/NOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common-utils/NOTES.md b/src/common-utils/NOTES.md index e9b27131e..f2c7aa72e 100644 --- a/src/common-utils/NOTES.md +++ b/src/common-utils/NOTES.md @@ -2,6 +2,11 @@ This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, RockyLinux, and Alpine Linux. +## 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 +these images have already allocated UID & GID 1000. Attempting to add this Feature with UID 1000 and/or GID 1000 on top of such a dev container image will result in an error when building the dev container. + ## Customizing the command prompt 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. From cfae3b0f0d016f36229060b0e2abf5d55c9304e3 Mon Sep 17 00:00:00 2001 From: Shunya Hayashi <70781900+devshun@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:53:50 +0900 Subject: [PATCH 010/361] update go tools (#559) * update go tools * update link * add unit tests for confirm installation * fix path install gotests * fix bump the minor version --- src/go/devcontainer-feature.json | 2 +- src/go/install.sh | 11 +++++++---- test/go/test.sh | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/go/devcontainer-feature.json b/src/go/devcontainer-feature.json index 92ed59154..963dc8948 100644 --- a/src/go/devcontainer-feature.json +++ b/src/go/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "go", - "version": "1.1.3", + "version": "1.2.0", "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 365421e4a..f79fc6f3d 100755 --- a/src/go/install.sh +++ b/src/go/install.sh @@ -191,15 +191,18 @@ else fi # Install Go tools that are isImportant && !replacedByGopls based on -# https://github.com/golang/vscode-go/blob/v0.31.1/src/goToolsInformation.ts +# https://github.com/golang/vscode-go/blob/v0.38.0/src/goToolsInformation.ts GO_TOOLS="\ golang.org/x/tools/gopls@latest \ honnef.co/go/tools/cmd/staticcheck@latest \ golang.org/x/lint/golint@latest \ github.com/mgechev/revive@latest \ - github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest \ - github.com/ramya-rao-a/go-outline@latest \ - github.com/go-delve/delve/cmd/dlv@latest" + github.com/go-delve/delve/cmd/dlv@latest \ + github.com/fatih/gomodifytags@latest \ + github.com/haya14busa/goplay/cmd/goplay@latest \ + github.com/cweill/gotests/gotests@latest \ + github.com/josharian/impl@latest" + if [ "${INSTALL_GO_TOOLS}" = "true" ]; then echo "Installing common Go tools..." export PATH=${TARGET_GOROOT}/bin:${PATH} diff --git a/test/go/test.sh b/test/go/test.sh index ccae6da84..0a9e04573 100755 --- a/test/go/test.sh +++ b/test/go/test.sh @@ -5,9 +5,24 @@ set -e # Optional: Import test library source dev-container-features-test-lib +# go check "version" go version + +# revive check "revive version" revive --version check "revive is installed at correct path" bash -c "which revive | grep /go/bin/revive" +# gomodifytags +check "gomodifytags is installed at correct path" bash -c "which gomodifytags | grep /go/bin/gomodifytags" + +# goplay +check "goplay is installed at correct path" bash -c "which goplay | grep /go/bin/goplay" + +# gotests +check "gotests is installed at correct path" bash -c "which gotests | grep /go/bin/gotests" + +# impl +check "impl is installed at correct path" bash -c "which impl | grep /go/bin/impl" + # Report result reportResults \ No newline at end of file From 852f1f0567280b465913b487d5f8c773122c36ab Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 31 May 2023 11:55:51 -0700 Subject: [PATCH 011/361] Automated documentation update (#563) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/common-utils/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common-utils/README.md b/src/common-utils/README.md index 9102c950b..1eddcbcc3 100644 --- a/src/common-utils/README.md +++ b/src/common-utils/README.md @@ -28,6 +28,11 @@ Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-r This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, RockyLinux, and Alpine Linux. +## 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 +these images have already allocated UID & GID 1000. Attempting to add this Feature with UID 1000 and/or GID 1000 on top of such a dev container image will result in an error when building the dev container. + ## Customizing the command prompt 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. From 367308c73606c03fa1a68e884d6ec37320a46b4f Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Fri, 16 Jun 2023 14:31:40 -0700 Subject: [PATCH 012/361] Add Debian Bookworm as a test base image (#575) test against debian bookworm --- .github/workflows/test-all.yaml | 4 ++-- .github/workflows/test-pr.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index ab51a737c..8dc206d86 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -42,11 +42,11 @@ jobs: ] baseImage: [ + "ubuntu:bionic", "ubuntu:focal", "ubuntu:jammy", - "ubuntu:bionic", "debian:11", - "debian:10", + "debian:12", "mcr.microsoft.com/devcontainers/base:ubuntu", "mcr.microsoft.com/devcontainers/base:debian", ] diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index fb586fb8b..6b91b612d 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -49,11 +49,11 @@ jobs: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} baseImage: [ + "ubuntu:bionic", "ubuntu:focal", "ubuntu:jammy", - "ubuntu:bionic", "debian:11", - "debian:10", + "debian:12", "mcr.microsoft.com/devcontainers/base:ubuntu", "mcr.microsoft.com/devcontainers/base:debian", ] From ee9f0bfbf09d60c242f2bd96d168ab0ab8c4f5c5 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Mon, 19 Jun 2023 11:23:49 -0700 Subject: [PATCH 013/361] Temporaily pin TFLint to 0.46.1 (#583) temporaily pin TFLint to 0.46.1 https://github.com/devcontainers/features/issues/581 --- src/terraform/devcontainer-feature.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 7ea73eea9..100bb02a8 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -20,10 +20,12 @@ "tflint": { "type": "string", "proposals": [ - "latest" + "latest", + "0.47.0", + "0.46.1" ], - "default": "latest", - "description": "Tflint version" + "default": "0.46.1", + "description": "Tflint version (Default value temporarily pinned to version 0.46.1: https://github.com/devcontainers/features/issues/581)" }, "terragrunt": { "type": "string", From 931262d20cb505880f44d8572e45cfbb393ae2fb Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Mon, 19 Jun 2023 18:24:44 +0000 Subject: [PATCH 014/361] release terraform --- src/terraform/devcontainer-feature.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 100bb02a8..523858382 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.3.0", + "version": "1.3.1", "name": "Terraform, tflint, and TFGrunt", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/terraform", "description": "Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects latest version and installs needed dependencies.", From 2fb386db11231e054012c85b0e1b3c2e879aef34 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Mon, 19 Jun 2023 11:33:35 -0700 Subject: [PATCH 015/361] Automated documentation update (#585) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/terraform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terraform/README.md b/src/terraform/README.md index e93058718..6236e4382 100644 --- a/src/terraform/README.md +++ b/src/terraform/README.md @@ -16,7 +16,7 @@ Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects la | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| | version | Terraform version | string | latest | -| tflint | Tflint version | string | latest | +| tflint | Tflint version (Default value temporarily pinned to version 0.46.1: https://github.com/devcontainers/features/issues/581) | string | 0.46.1 | | terragrunt | Terragrunt version | string | latest | | 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 | From 2258fcb040d74de01dbfc9df7d8800d90a13d56c Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Wed, 21 Jun 2023 00:34:07 +0700 Subject: [PATCH 016/361] feat(rust): add up to version 1.70 options (#579) --- src/rust/devcontainer-feature.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/rust/devcontainer-feature.json b/src/rust/devcontainer-feature.json index fd5d02612..bc9044c60 100644 --- a/src/rust/devcontainer-feature.json +++ b/src/rust/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "rust", - "version": "1.0.11", + "version": "1.0.12", "name": "Rust", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/rust", "description": "Installs Rust, common Rust utilities, and their required dependencies", @@ -10,6 +10,13 @@ "proposals": [ "latest", "none", + "1.70", + "1.69", + "1.68", + "1.67", + "1.66", + "1.65", + "1.64", "1.63", "1.62", "1.61" From d934503a050ba84e6b42a006aacd891c4088eb62 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Wed, 21 Jun 2023 11:13:46 -0700 Subject: [PATCH 017/361] Terraform: Add cosign integrity check for TFLint (#586) * add cosign integrity check for tflint * fallback to gpg verification --- src/terraform/devcontainer-feature.json | 6 +-- src/terraform/install.sh | 61 +++++++++++++++++++++---- test/terraform/older_tflint.sh | 13 ++++++ test/terraform/scenarios.json | 8 ++++ 4 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 test/terraform/older_tflint.sh diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 523858382..00f6ba478 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.3.1", + "version": "1.3.2", "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.", @@ -24,8 +24,8 @@ "0.47.0", "0.46.1" ], - "default": "0.46.1", - "description": "Tflint version (Default value temporarily pinned to version 0.46.1: https://github.com/devcontainers/features/issues/581)" + "default": "latest", + "description": "Tflint version (https://github.com/terraform-linters/tflint/releases)" }, "terragrunt": { "type": "string", diff --git a/src/terraform/install.sh b/src/terraform/install.sh index 75653c392..81682e80e 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -158,6 +158,26 @@ check_packages() { fi } +# Install 'cosign' for validating signatures +# https://docs.sigstore.dev/cosign/overview/ +ensure_cosign() { + check_packages curl ca-certificates gnupg2 + + if ! type cosign > /dev/null 2>&1; then + echo "Installing cosign..." + local LATEST_COSIGN_VERSION=$(curl https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d : -f2 | tr -d "v\", ") + curl -L "https://github.com/sigstore/cosign/releases/latest/download/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb" -o /tmp/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb + + dpkg -i /tmp/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb + rm /tmp/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb + fi + if ! type cosign > /dev/null 2>&1; then + echo "(!) Failed to install cosign." + exit 1 + fi + cosign version +} + # Ensure apt is in non-interactive to avoid prompts export DEBIAN_FRONTEND=noninteractive @@ -198,17 +218,42 @@ if [ "${TFLINT_VERSION}" != "none" ]; then TFLINT_FILENAME="tflint_linux_${architecture}.zip" curl -sSL -o /tmp/tf-downloads/${TFLINT_FILENAME} https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/${TFLINT_FILENAME} if [ "${TFLINT_SHA256}" != "dev-mode" ]; then - if [ "${TFLINT_SHA256}" = "automatic" ]; then - curl -sSL -o tflint_key "${TFLINT_GPG_KEY_URI}" - gpg -q --import tflint_key - curl -sSL -o tflint_checksums.txt https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt - curl -sSL -o tflint_checksums.txt.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.sig - gpg --verify tflint_checksums.txt.sig tflint_checksums.txt - else + + 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 + + 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 - sha256sum --ignore-missing -c tflint_checksums.txt fi + unzip /tmp/tf-downloads/${TFLINT_FILENAME} mv -f tflint /usr/local/bin/ fi diff --git a/test/terraform/older_tflint.sh b/test/terraform/older_tflint.sh new file mode 100644 index 000000000..20009c995 --- /dev/null +++ b/test/terraform/older_tflint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +check "terraform" terraform -version + +check "tflint" tflint --version + +# Report result +reportResults \ No newline at end of file diff --git a/test/terraform/scenarios.json b/test/terraform/scenarios.json index 897763518..0a4d8956c 100644 --- a/test/terraform/scenarios.json +++ b/test/terraform/scenarios.json @@ -22,5 +22,13 @@ "installTerraformDocs": true } } + }, + "older_tflint": { + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "features": { + "terraform": { + "tflint": "0.40.0" + } + } } } \ No newline at end of file From de40074875b7add6b1a0d2ed3496ad05699a02c2 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Fri, 23 Jun 2023 16:16:30 -0700 Subject: [PATCH 018/361] Automated documentation update (#587) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/terraform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terraform/README.md b/src/terraform/README.md index 6236e4382..2201c8db8 100644 --- a/src/terraform/README.md +++ b/src/terraform/README.md @@ -16,7 +16,7 @@ Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects la | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| | version | Terraform version | string | latest | -| tflint | Tflint version (Default value temporarily pinned to version 0.46.1: https://github.com/devcontainers/features/issues/581) | string | 0.46.1 | +| tflint | Tflint version (https://github.com/terraform-linters/tflint/releases) | string | latest | | terragrunt | Terragrunt version | string | latest | | 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 | From ccce957d7e4bc1357559b361d4b219872ba57739 Mon Sep 17 00:00:00 2001 From: Josh Spicer Date: Tue, 27 Jun 2023 14:31:18 -0700 Subject: [PATCH 019/361] Azure CLI: Compatibility with Debian `bookworm` (#578) * add flag * try to install via apt version of pipx (will only work on bookworm) * _REMOTE_USER aware * dont change more than you need to,josh * increment azure-cli minor version (v1.1.0) * install globally * make sure env variables are in scope --- src/azure-cli/devcontainer-feature.json | 2 +- src/azure-cli/install.sh | 56 +++++++++++++++---- test/azure-cli/install_bicep.sh | 2 + test/azure-cli/install_extensions.sh | 2 + test/azure-cli/install_extensions_bookworm.sh | 8 +++ test/azure-cli/scenarios.json | 10 ++++ 6 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 test/azure-cli/install_extensions_bookworm.sh diff --git a/src/azure-cli/devcontainer-feature.json b/src/azure-cli/devcontainer-feature.json index 9ab2cc39b..901ba4fc1 100644 --- a/src/azure-cli/devcontainer-feature.json +++ b/src/azure-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "azure-cli", - "version": "1.0.8", + "version": "1.1.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 31bc6d864..181d62f05 100755 --- a/src/azure-cli/install.sh +++ b/src/azure-cli/install.sh @@ -25,6 +25,13 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi +if [ -z "${_REMOTE_USER}" ]; then + echo -e 'Feature script must be executed by a tool that implements the dev container specification. See https://containers.dev/ for more information.' + exit 1 +fi + +echo "Effective REMOTE_USER: ${_REMOTE_USER}" + # Get central common setting get_common_setting() { if [ "${common_settings_file_loaded}" != "true" ]; then @@ -57,8 +64,6 @@ check_packages() { fi } -export DEBIAN_FRONTEND=noninteractive - # Soft version matching that resolves a version for a given package in the *current apt-cache* # Return value is stored in first argument (the unprocessed version) apt_cache_version_soft_match() { @@ -127,7 +132,39 @@ install_using_apt() { fi } -install_using_pip() { +install_using_pip_strategy() { + local ver="" + if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then + # Empty, meaning grab the "latest" in the apt repo + ver="" + else + ver="==${AZ_VERSION}" + fi + + install_with_pipx "${ver}" || install_with_complete_python_installation "${ver}" || return 1 +} + +install_with_pipx() { + echo "(*) Attempting to install globally with pipx..." + local ver="$1" + export + local + + if ! type pipx > /dev/null 2>&1; then + echo "(*) Installing pipx..." + check_packages pipx + pipx ensurepath # Ensures PIPX_BIN_DIR is on the PATH + fi + + PIPX_HOME="/usr/local/pipx" \ + PIPX_BIN_DIR=/usr/local/bin \ + pipx install azure-cli${ver} + + echo "(*) Finished installing globally with pipx." +} + +install_with_complete_python_installation() { + local ver="$1" echo "(*) No pre-built binaries available in apt-cache. Installing via pip3." if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then apt_get_update @@ -144,25 +181,20 @@ install_using_pip() { pipx_bin=/tmp/pip-tmp/bin/pipx fi - if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then - # Empty, meaning grab the "latest" in the apt repo - ver="" - else - ver="==${AZ_VERSION}" - fi - set +e ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' -f azure-cli${ver} # Fail gracefully if [ "$?" != 0 ]; then - echo "Could not install azure-cli${ver} via pip" + echo "Could not install azure-cli${ver} via pip3" rm -rf /tmp/pip-tmp return 1 fi set -e } +export DEBIAN_FRONTEND=noninteractive + # See if we're on x86_64 and if so, install via apt-get, otherwise use pip3 echo "(*) Installing Azure CLI..." . /etc/os-release @@ -176,7 +208,7 @@ fi if [ "${use_pip}" = "true" ]; then AZ_VERSION=${CACHED_AZURE_VERSION} - install_using_pip + install_using_pip_strategy if [ "$?" != 0 ]; then echo "Please provide a valid version for your distribution ${ID} ${VERSION_CODENAME} (${architecture})." diff --git a/test/azure-cli/install_bicep.sh b/test/azure-cli/install_bicep.sh index 955507fda..0430c64da 100644 --- a/test/azure-cli/install_bicep.sh +++ b/test/azure-cli/install_bicep.sh @@ -8,6 +8,8 @@ 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 + # Bicep-specific tests check "bicep" bicep --version check "az bicep" az bicep version diff --git a/test/azure-cli/install_extensions.sh b/test/azure-cli/install_extensions.sh index 1e66e2633..58d1cfff2 100644 --- a/test/azure-cli/install_extensions.sh +++ b/test/azure-cli/install_extensions.sh @@ -8,6 +8,8 @@ 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 + # Extension-specific tests check "aks-preview" az extension show --name aks-preview check "amg" az extension show --name amg diff --git a/test/azure-cli/install_extensions_bookworm.sh b/test/azure-cli/install_extensions_bookworm.sh new file mode 100644 index 000000000..3c281e685 --- /dev/null +++ b/test/azure-cli/install_extensions_bookworm.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +# Import test library for `check` command +source dev-container-features-test-lib + +./install_extensions.sh \ No newline at end of file diff --git a/test/azure-cli/scenarios.json b/test/azure-cli/scenarios.json index cc1fd7512..231345225 100644 --- a/test/azure-cli/scenarios.json +++ b/test/azure-cli/scenarios.json @@ -9,6 +9,16 @@ } } }, + "install_extensions_bookworm": { + "image": "mcr.microsoft.com/devcontainers/base:bookworm", + "user": "vscode", + "features": { + "azure-cli": { + "version": "latest", + "extensions": "aks-preview,amg,containerapp" + } + } + }, "install_bicep": { "image": "mcr.microsoft.com/devcontainers/base:jammy", "user": "vscode", From 972fdffa717bad7d5db8d3f8e88a65d7953a34cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melissa=20Weber=20Mendon=C3=A7a?= Date: Wed, 28 Jun 2023 13:17:04 -0300 Subject: [PATCH 020/361] Add instructions to set up port forwarding on devcontainer.json file (#593) --- src/desktop-lite/NOTES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/desktop-lite/NOTES.md b/src/desktop-lite/NOTES.md index 891ce6c4a..9080c4335 100644 --- a/src/desktop-lite/NOTES.md +++ b/src/desktop-lite/NOTES.md @@ -6,6 +6,16 @@ This feature provides two ways of connecting to the desktop environment it adds. 1. Open the ports view in your tool, select the noVNC port, and click the Globe icon. 1. In the browser that appears, click the **Connect** button and enter the desktop password (`vscode` by default). +To set up the `6080` port from your `devcontainer.json` file, include the following: +```json + "forwardPorts": [6080], + "portsAttributes": { + "6080": { + "label": "desktop" + } + } +``` + You can also connect to the desktop using a [VNC viewer](https://www.realvnc.com/en/connect/download/viewer/). To do so: 1. Connect to the environment from a desktop tool that supports the dev container spec (e.g., VS Code client). From 610286c369717c64610133e9b898a84c9dbecea3 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 28 Jun 2023 14:17:06 -0700 Subject: [PATCH 021/361] Automated documentation update (#596) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/desktop-lite/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/desktop-lite/README.md b/src/desktop-lite/README.md index ad05e4ff2..48397929b 100644 --- a/src/desktop-lite/README.md +++ b/src/desktop-lite/README.md @@ -29,6 +29,16 @@ This feature provides two ways of connecting to the desktop environment it adds. 1. Open the ports view in your tool, select the noVNC port, and click the Globe icon. 1. In the browser that appears, click the **Connect** button and enter the desktop password (`vscode` by default). +To set up the `6080` port from your `devcontainer.json` file, include the following: +```json + "forwardPorts": [6080], + "portsAttributes": { + "6080": { + "label": "desktop" + } + } +``` + You can also connect to the desktop using a [VNC viewer](https://www.realvnc.com/en/connect/download/viewer/). To do so: 1. Connect to the environment from a desktop tool that supports the dev container spec (e.g., VS Code client). From b6dbe7051cb3c0491749409351804eef942b4160 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:16:30 -0700 Subject: [PATCH 022/361] Bump mpmath from 1.2.1 to 1.3.0 in /test/oryx/sample-python (#554) Bumps [mpmath](https://github.com/fredrik-johansson/mpmath) from 1.2.1 to 1.3.0. - [Release notes](https://github.com/fredrik-johansson/mpmath/releases) - [Changelog](https://github.com/mpmath/mpmath/blob/master/CHANGES) - [Commits](https://github.com/fredrik-johansson/mpmath/compare/1.2.1...1.3.0) --- updated-dependencies: - dependency-name: mpmath dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/oryx/sample-python/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/oryx/sample-python/requirements.txt b/test/oryx/sample-python/requirements.txt index c8a04f68a..0f34acb42 100644 --- a/test/oryx/sample-python/requirements.txt +++ b/test/oryx/sample-python/requirements.txt @@ -1,2 +1,2 @@ -mpmath==1.2.1 +mpmath==1.3.0 sympy==1.11.1 From ef76ff97b60f43c7dcff6691427d9fc54e9b2503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 29 Jun 2023 18:00:56 -0400 Subject: [PATCH 023/361] Change Ruby extension to ruby-lsp (#566) This is the extension that is recommended in the VSCode documentation now. https://code.visualstudio.com/docs/languages/ruby --- src/ruby/devcontainer-feature.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ruby/devcontainer-feature.json b/src/ruby/devcontainer-feature.json index e9fafeefd..fdf8b26b3 100644 --- a/src/ruby/devcontainer-feature.json +++ b/src/ruby/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ruby", - "version": "1.0.10", + "version": "1.1.0", "name": "Ruby (via rvm)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/ruby", "description": "Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies.", @@ -21,7 +21,8 @@ "customizations": { "vscode": { "extensions": [ - "rebornix.Ruby" + "rebornix.Ruby", + "shopify.ruby-lsp" ] } }, From 8a51aa906676280364df60010575b7dad3162f37 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Thu, 29 Jun 2023 15:08:34 -0700 Subject: [PATCH 024/361] Automated documentation update (#602) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/ruby/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ruby/README.md b/src/ruby/README.md index 8e8ebe2c3..8184036c6 100644 --- a/src/ruby/README.md +++ b/src/ruby/README.md @@ -22,6 +22,7 @@ Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies. ### VS Code Extensions - `rebornix.Ruby` +- `shopify.ruby-lsp` From 300814be97eaa5b7780886bd2eb37e96b523b3e7 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Thu, 29 Jun 2023 15:14:11 -0700 Subject: [PATCH 025/361] Dotnet: Fix Feature failures on ubuntu:jammy (when installing using apt) (#590) * fix several bugs * fix test: .NET is now installed from ubuntu feeds * add comment ; use dev containers image * address comments * revert debugging cmd * fix test for bookworm --- src/dotnet/devcontainer-feature.json | 2 +- src/dotnet/install.sh | 32 +++++++++++++++++++++++---- test/dotnet/install_dotnet_6_jammy.sh | 17 ++++++++++++++ test/dotnet/install_dotnet_7_jammy.sh | 2 +- test/dotnet/install_dotnet_latest.sh | 2 +- test/dotnet/scenarios.json | 8 +++++++ test/dotnet/test.sh | 2 +- 7 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 test/dotnet/install_dotnet_6_jammy.sh diff --git a/src/dotnet/devcontainer-feature.json b/src/dotnet/devcontainer-feature.json index 4aa264c13..4f484ccba 100644 --- a/src/dotnet/devcontainer-feature.json +++ b/src/dotnet/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "dotnet", - "version": "1.1.3", + "version": "1.1.4", "name": "Dotnet CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/dotnet", "description": "Installs the .NET CLI. Provides option of installing sdk or runtime, and option of versions to install. Uses latest version of .NET sdk as defaults to install.", diff --git a/src/dotnet/install.sh b/src/dotnet/install.sh index 1c7430aa0..178994d71 100644 --- a/src/dotnet/install.sh +++ b/src/dotnet/install.sh @@ -33,6 +33,16 @@ DOTNET_VERSION_CODENAMES_REQUIRE_OLDER_LIBSSL_1="buster bullseye bionic focal hi # alongside DOTNET_VERSION, but not set as default. ADDITIONAL_VERSIONS=${ADDITIONALVERSIONS:-""} +APT_PREFERENCES_CONTENT="$(cat << 'EOF' +Package: * +Pin: origin "packages.microsoft.com" +Pin-Priority: 1001 +EOF +)" + +APT_PREFERENCES_FILE_ADDED="false" +APT_PREFERENCES_UPDATED="false" + set -e # Clean up @@ -147,9 +157,9 @@ apt_cache_package_and_version_soft_match() { . /etc/os-release local architecture="$(dpkg --print-architecture)" + apt-get update -y major_minor_version="$(echo "${requested_version}" | cut -d "." --field=1,2)" - package_name="$(apt-cache search "${partial_package_name}-[0-9].[0-9]" | awk -F" - " '{print $1}' | grep -m 1 "${partial_package_name}-${major_minor_version}")" - + package_name="$(apt-cache search "${partial_package_name}-[0-9].[0-9]$" | awk -F" - " '{print $1}' | grep -m 1 "${partial_package_name}-${major_minor_version}")" dot_escaped="${requested_version//./\\.}" dot_plus_escaped="${dot_escaped//+/\\+}" # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/ @@ -193,6 +203,12 @@ install_using_apt() { # Import key safely and import Microsoft apt repo 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/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list + + [ ! -f /etc/apt/preferences ] && APT_PREFERENCES_FILE_ADDED="true" + APT_PREFERENCES_UPDATED="true" + + # See https://github.com/dotnet/core/issues/7699 + echo "${APT_PREFERENCES_CONTENT}" >> /etc/apt/preferences apt-get update -y fi @@ -233,8 +249,8 @@ install_using_apt() { mkdir -p "${TARGET_DOTNET_ROOT}" local dotnet_installed_version="$(dotnet --version)" # See if its the distro version - if [[ "$(dotnet --info)" == *"Base Path: /usr/lib/dotnet/dotnet${dotnet_installed_version:0:1}-${dotnet_installed_version}"* ]]; then - ln -s "/usr/lib/dotnet/dotnet${dotnet_installed_version:0:1}" "${CURRENT_DIR}" + if [[ "$(dotnet --info)" == *"Base Path: /usr/lib/dotnet/${DOTNET_SDK_OR_RUNTIME}/${dotnet_installed_version}"* ]]; then + ln -s "/usr/lib/dotnet" "${CURRENT_DIR}" else # Location used by MS repo versions ln -s "/usr/share/dotnet" "${CURRENT_DIR}" @@ -463,6 +479,14 @@ if [ "${CHANGE_OWNERSHIP}" = "true" ]; then fi # Clean up +if [ "${APT_PREFERENCES_UPDATED}" = "true" ]; then + if [ "${APT_PREFERENCES_FILE_ADDED}" = "true" ]; then + rm -f /etc/apt/preferences + else + head -n -3 /etc/apt/preferences > /tmp/preferences-temp && mv /tmp/preferences-temp /etc/apt/preferences + fi +fi + rm -rf /var/lib/apt/lists/* echo "Done!" diff --git a/test/dotnet/install_dotnet_6_jammy.sh b/test/dotnet/install_dotnet_6_jammy.sh new file mode 100644 index 000000000..fd1063473 --- /dev/null +++ b/test/dotnet/install_dotnet_6_jammy.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +check "dotnet sdks" dotnet --list-sdks +check "some major version of dotnet 6 is installed" bash -c "dotnet --list-sdks | grep '6\.[0-9]*\.[0-9]*'" +check "dotnet version 6 installed" bash -c "ls -l /usr/lib/dotnet/sdk | grep '6\.[0-9]*\.[0-9]*'" + +# Verify current symlink exists and works +check "current link dotnet" /usr/local/dotnet/current/dotnet --info +check "current link sdk" ls -l /usr/local/dotnet/current/sdk + +# Report result +reportResults diff --git a/test/dotnet/install_dotnet_7_jammy.sh b/test/dotnet/install_dotnet_7_jammy.sh index 2c015fc0e..139216885 100644 --- a/test/dotnet/install_dotnet_7_jammy.sh +++ b/test/dotnet/install_dotnet_7_jammy.sh @@ -7,7 +7,7 @@ source dev-container-features-test-lib check "dotnet sdks" dotnet --list-sdks check "some major version of dotnet 7 is installed" bash -c "dotnet --list-sdks | grep '7\.[0-9]*\.[0-9]*'" -check "dotnet version 7 installed" bash -c "ls -l /usr/share/dotnet/sdk | grep '7\.[0-9]*\.[0-9]*'" +check "dotnet version 7 installed" bash -c "ls -l /usr/lib/dotnet/sdk | grep '7\.[0-9]*\.[0-9]*'" # Verify current symlink exists and works check "current link dotnet" /usr/local/dotnet/current/dotnet --info diff --git a/test/dotnet/install_dotnet_latest.sh b/test/dotnet/install_dotnet_latest.sh index 2c015fc0e..139216885 100644 --- a/test/dotnet/install_dotnet_latest.sh +++ b/test/dotnet/install_dotnet_latest.sh @@ -7,7 +7,7 @@ source dev-container-features-test-lib check "dotnet sdks" dotnet --list-sdks check "some major version of dotnet 7 is installed" bash -c "dotnet --list-sdks | grep '7\.[0-9]*\.[0-9]*'" -check "dotnet version 7 installed" bash -c "ls -l /usr/share/dotnet/sdk | grep '7\.[0-9]*\.[0-9]*'" +check "dotnet version 7 installed" bash -c "ls -l /usr/lib/dotnet/sdk | grep '7\.[0-9]*\.[0-9]*'" # Verify current symlink exists and works check "current link dotnet" /usr/local/dotnet/current/dotnet --info diff --git a/test/dotnet/scenarios.json b/test/dotnet/scenarios.json index 37f83d5a9..afee26f84 100644 --- a/test/dotnet/scenarios.json +++ b/test/dotnet/scenarios.json @@ -48,6 +48,14 @@ } } }, + "install_dotnet_6_jammy": { + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "features": { + "dotnet": { + "version": "6" + } + } + }, "install_dotnet_latest": { "image": "ubuntu:jammy", "features": { diff --git a/test/dotnet/test.sh b/test/dotnet/test.sh index 7eb0cf6d5..f433654e6 100755 --- a/test/dotnet/test.sh +++ b/test/dotnet/test.sh @@ -11,7 +11,7 @@ check "sdks" dotnet --list-sdks check "version" dotnet --version echo "Validating expected version present..." -check "some major version of dotnet 7 is installed" bash -c "dotnet --version | grep '7\.[0-9]*\.[0-9]*'" +check "some major version of dotnet (7/8) is installed" bash -c "dotnet --version | grep '[7-8]\.[0-9]*\.[0-9]*'" # Verify current symlink exists and works check "current link dotnet" /usr/local/dotnet/current/dotnet --info From c4ffeebdacc3c7c020ee7c286aae88959aa91671 Mon Sep 17 00:00:00 2001 From: Kazuma Watanabe Date: Fri, 7 Jul 2023 02:23:36 +0900 Subject: [PATCH 026/361] Pin version of TFLint's GPG key URL (#605) --- src/terraform/devcontainer-feature.json | 4 ++-- src/terraform/install.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index 00f6ba478..b1e6df2c8 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.3.2", + "version": "1.3.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.", @@ -74,4 +74,4 @@ "installsAfter": [ "ghcr.io/devcontainers/features/common-utils" ] -} \ No newline at end of file +} diff --git a/src/terraform/install.sh b/src/terraform/install.sh index 81682e80e..82e3e8d51 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -27,7 +27,7 @@ TFSEC_SHA256="${TFSEC_SHA256:-"automatic"}" TERRAFORM_DOCS_SHA256="${TERRAFORM_DOCS_SHA256:-"automatic"}" TERRAFORM_GPG_KEY="72D7468F" -TFLINT_GPG_KEY_URI="https://raw.githubusercontent.com/terraform-linters/tflint/master/8CE69160EB3F2FE9.key" +TFLINT_GPG_KEY_URI="https://raw.githubusercontent.com/terraform-linters/tflint/v0.46.1/8CE69160EB3F2FE9.key" GPG_KEY_SERVERS="keyserver hkps://keyserver.ubuntu.com keyserver hkps://keys.openpgp.org keyserver hkps://keyserver.pgp.com" From 07693965d74d289e9a4035907d2414d9683e0773 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Tue, 11 Jul 2023 11:16:23 -0700 Subject: [PATCH 027/361] Nvidia-cuda: Update notes regarding gpu enablement (#608) * Nvidia-cuda: Update notes regarding gpu enablement * use GPU hostRequirements instead * nit * use gpu:optional instead --- src/nvidia-cuda/NOTES.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nvidia-cuda/NOTES.md b/src/nvidia-cuda/NOTES.md index cab2bbec7..db2b843a2 100644 --- a/src/nvidia-cuda/NOTES.md +++ b/src/nvidia-cuda/NOTES.md @@ -10,14 +10,19 @@ Follow [NVIDIA's instructions to install the NVIDIA Container Toolkit](https://d ### Enable GPU passthrough -Enable GPU passthrough to your devcontainer by adding `["--gpus", "all"]` to your devcontainer's `runArgs` property. Here's an example of a devcontainer with this property: +Enable GPU passthrough to your devcontainer by using `hostRequirements`. Here's an example of a devcontainer with this property: ```json { - "runArgs": ["--gpus", "all"] + "hostRequirements": { + "gpu": "optional" + } } ``` +> Note: Setting `gpu` property's value to `true` will work with GPU machine types, but fail with CPUs. Hence, setting it to `optional` works in both cases. See [schema](https://containers.dev/implementors/json_schema/#base-schema) for more configuration details. + + ## OS Support From 0e8940abead5068668bd78632ec9b20757541c95 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Tue, 11 Jul 2023 12:50:29 -0700 Subject: [PATCH 028/361] Automated documentation update (#610) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/nvidia-cuda/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nvidia-cuda/README.md b/src/nvidia-cuda/README.md index 38a989a04..7a0343de4 100644 --- a/src/nvidia-cuda/README.md +++ b/src/nvidia-cuda/README.md @@ -32,14 +32,19 @@ Follow [NVIDIA's instructions to install the NVIDIA Container Toolkit](https://d ### Enable GPU passthrough -Enable GPU passthrough to your devcontainer by adding `["--gpus", "all"]` to your devcontainer's `runArgs` property. Here's an example of a devcontainer with this property: +Enable GPU passthrough to your devcontainer by using `hostRequirements`. Here's an example of a devcontainer with this property: ```json { - "runArgs": ["--gpus", "all"] + "hostRequirements": { + "gpu": "optional" + } } ``` +> Note: Setting `gpu` property's value to `true` will work with GPU machine types, but fail with CPUs. Hence, setting it to `optional` works in both cases. See [schema](https://containers.dev/implementors/json_schema/#base-schema) for more configuration details. + + ## OS Support From 7fcec6e7c9dd587b24096597367ef42e4a37465b Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 19 Jul 2023 08:50:22 -0700 Subject: [PATCH 029/361] Fix: Docker-in-docker for python - Arm64 (#618) Fix: Docker-in-docker for python:3.11 - Arm64 --- src/docker-in-docker/devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 1555cd6e7..334982161 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.2.0", + "version": "2.2.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 9ad807e3c..6c889f5ac 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -265,7 +265,21 @@ if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then pip3 install --disable-pip-version-check --no-cache-dir --user pipx pipx_bin=/tmp/pip-tmp/bin/pipx fi - ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose + + set +e + ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose + exit_code=$? + set -e + + if [ ${exit_code} -ne 0 ]; then + # Temporary: https://github.com/devcontainers/features/issues/616 + # See https://github.com/yaml/pyyaml/issues/601 + echo "(*) Failed to install docker-compose via pipx. Trying via pip3..." + + export PYTHONUSERBASE=/usr/local + pip3 install --disable-pip-version-check --no-cache-dir --user "Cython<3.0" pyyaml wheel docker-compose --no-build-isolation + fi + rm -rf /tmp/pip-tmp else compose_v1_version="1" From ea2d79070ec4e91361412a88ead2ec9b78266b92 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Fri, 21 Jul 2023 17:37:10 -0700 Subject: [PATCH 030/361] Azure-cli - Temporary quick fix for jammy (ARM) (#626) --- src/azure-cli/devcontainer-feature.json | 7 ++++++- src/azure-cli/install.sh | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/azure-cli/devcontainer-feature.json b/src/azure-cli/devcontainer-feature.json index 901ba4fc1..09c295c21 100644 --- a/src/azure-cli/devcontainer-feature.json +++ b/src/azure-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "azure-cli", - "version": "1.1.0", + "version": "1.2.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.", @@ -22,6 +22,11 @@ "type": "boolean", "description": "Optionally install Azure Bicep", "default": false + }, + "installUsingPython": { + "type": "boolean", + "description": "Install Azure CLI using Python instead of pipx", + "default": true } }, "customizations": { diff --git a/src/azure-cli/install.sh b/src/azure-cli/install.sh index 181d62f05..9b0d72db3 100755 --- a/src/azure-cli/install.sh +++ b/src/azure-cli/install.sh @@ -15,7 +15,7 @@ rm -rf /var/lib/apt/lists/* AZ_VERSION=${VERSION:-"latest"} AZ_EXTENSIONS=${EXTENSIONS} AZ_INSTALLBICEP=${INSTALLBICEP:-false} - +INSTALL_USING_PYTHON=${INSTALL_USING_PYTHON:-true} MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" AZCLI_ARCHIVE_ARCHITECTURES="amd64" AZCLI_ARCHIVE_VERSION_CODENAMES="stretch buster bullseye bionic focal jammy" @@ -141,7 +141,12 @@ install_using_pip_strategy() { ver="==${AZ_VERSION}" fi - install_with_pipx "${ver}" || install_with_complete_python_installation "${ver}" || return 1 + # Temprary quick fix for https://github.com/devcontainers/features/issues/624 + if [ "${INSTALL_USING_PYTHON}" = "true" ]; then + install_with_complete_python_installation "${ver}" || install_with_pipx "${ver}" || return 1 + else + install_with_pipx "${ver}" || install_with_complete_python_installation "${ver}" || return 1 + fi } install_with_pipx() { From 836ea1e9faea580509b59f7df1e3b29f19c00e17 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 26 Jul 2023 14:43:43 -0700 Subject: [PATCH 031/361] Automated documentation update (#627) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/azure-cli/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/azure-cli/README.md b/src/azure-cli/README.md index 4a9de7b96..c3e23db8a 100644 --- a/src/azure-cli/README.md +++ b/src/azure-cli/README.md @@ -18,6 +18,7 @@ Installs the Azure CLI along with needed dependencies. Useful for base Dockerfil | version | Select or enter an Azure CLI version. (Available versions may vary by Linux distribution.) | string | latest | | extensions | Optional comma separated list of Azure CLI extensions to install in profile. | string | - | | installBicep | Optionally install Azure Bicep | boolean | false | +| installUsingPython | Install Azure CLI using Python instead of pipx | boolean | true | ## Customizations From 685fa2e0f151b4545b444135a5ef6877de20e851 Mon Sep 17 00:00:00 2001 From: Krikchai Pongtaveewould Date: Thu, 3 Aug 2023 05:12:14 +0700 Subject: [PATCH 032/361] Fix: common-utils configureZshAsDefaultShell option not working on alpine linux image (#557) * alpine defaultZsh fix * common-utils: replace pam.d/chsh permission with `sufficient` * common-utils: revert match auth sufficient * common-utils: append auth string instead of replacing a whole file * fix grep 2nd param * fix: line checking condition --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 8 ++++++++ test/common-utils/alpine-base-zsh-default.sh | 13 +++++++++++++ test/common-utils/scenarios.json | 13 +++++++++++-- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 test/common-utils/alpine-base-zsh-default.sh diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index aadc1bc27..a924aa77a 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.0.10", + "version": "2.0.11", "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 144478ee1..6d5ac0719 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -437,6 +437,14 @@ if [ "${INSTALL_ZSH}" = "true" ]; then fi if [ "${CONFIGURE_ZSH_AS_DEFAULT_SHELL}" == "true" ]; then + # Fixing chsh always asking for a password on alpine linux + # ref: https://askubuntu.com/questions/812420/chsh-always-asking-a-password-and-get-pam-authentication-failure. + if [ ! -f "/etc/pam.d/chsh" ] || ! grep -Eq '^auth(.*)pam_rootok\.so$' /etc/pam.d/chsh; then + echo "auth sufficient pam_rootok.so" >> /etc/pam.d/chsh + elif [[ -n "$(awk '/^auth(.*)pam_rootok\.so$/ && !/^auth[[:blank:]]+sufficient[[:blank:]]+pam_rootok\.so$/' /etc/pam.d/chsh)" ]]; then + awk '/^auth(.*)pam_rootok\.so$/ { $2 = "sufficient" } { print }' /etc/pam.d/chsh > /tmp/chsh.tmp && mv /tmp/chsh.tmp /etc/pam.d/chsh + fi + chsh --shell /bin/zsh ${USERNAME} fi diff --git a/test/common-utils/alpine-base-zsh-default.sh b/test/common-utils/alpine-base-zsh-default.sh new file mode 100644 index 000000000..9fc00a267 --- /dev/null +++ b/test/common-utils/alpine-base-zsh-default.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "alpine default shell zsh" \ + bash -c "getent passwd $(whoami) | awk -F : '{ print $7 }' | grep '/bin/zsh'" + +# Report result +reportResults diff --git a/test/common-utils/scenarios.json b/test/common-utils/scenarios.json index 21cb9408a..9800242e9 100644 --- a/test/common-utils/scenarios.json +++ b/test/common-utils/scenarios.json @@ -143,6 +143,7 @@ "build": { "dockerfile": "Dockerfile" }, + "remoteUser": "vscode", "features": { "common-utils": { "username": "vscode", @@ -151,7 +152,15 @@ "upgradePackages": true, "installZsh": true } - }, - "remoteUser": "vscode" + } + }, + "alpine-base-zsh-default": { + "image": "mcr.microsoft.com/devcontainers/base:alpine", + "remoteUser": "vscode", + "features": { + "common-utils": { + "configureZshAsDefaultShell": true + } + } } } From 29859aff280dc187d6f508900f8600ebd04c56b7 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 7 Aug 2023 23:12:13 +0100 Subject: [PATCH 033/361] Fix 2 instances of a typo in the desktop-lite NOTES and README (#640) * Amend 'is installed is' to 'installed is'. * Undo typo fix from README Co-authored-by: Samruddhi Khandale --------- Co-authored-by: Samruddhi Khandale --- src/desktop-lite/NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop-lite/NOTES.md b/src/desktop-lite/NOTES.md index 9080c4335..924ce9b6e 100644 --- a/src/desktop-lite/NOTES.md +++ b/src/desktop-lite/NOTES.md @@ -25,7 +25,7 @@ You can also connect to the desktop using a [VNC viewer](https://www.realvnc.com ## Customizing Fluxbox -The window manager is installed is [Fluxbox](http://fluxbox.org/). **Right-click** to see the application menu. In addition, any UI-based commands you execute inside the dev container will automatically appear on the desktop. +The window manager installed is [Fluxbox](http://fluxbox.org/). **Right-click** to see the application menu. In addition, any UI-based commands you execute inside the dev container will automatically appear on the desktop. You can customize the desktop using Fluxbox configuration files. The configuration files are located in the `.fluxbox` folder of the home directory of the user you using to connect to the dev container (`$HOME/.fluxbox`). From bbb24d10d417452ef23a8c506fd0e2b141b71f15 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Mon, 7 Aug 2023 15:59:58 -0700 Subject: [PATCH 034/361] Docker-in-docker: Add retries to the docker-init script until docker daemon starts (#637) * Docker-in-docker: Add retries until docker daemon starts * address comments --- .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 30 +++++++++++++---- test/docker-in-docker/docker_retry.sh | 32 +++++++++++++++++++ test/docker-in-docker/scenarios.json | 7 +++- .../test-scripts/docker-test-init.sh | 26 +++++++++++++++ 5 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 test/docker-in-docker/docker_retry.sh create mode 100644 test/docker-in-docker/test-scripts/docker-test-init.sh diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 334982161..b5e0aeea6 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.2.1", + "version": "2.3.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 6c889f5ac..f7e5a7d52 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -5,7 +5,7 @@ #------------------------------------------------------------------------------------------------------------- # # Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md -# Maintainer: The VS Code and Codespaces Teams +# Maintainer: The Dev Container spec maintainers DOCKER_VERSION="${VERSION:-"latest"}" # The Docker/Moby Engine + CLI should match in version @@ -423,12 +423,28 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU INNEREOF )" -# Start using sudo if not invoked as root -if [ "$(id -u)" -ne 0 ]; then - sudo /bin/sh -c "${dockerd_start}" -else - eval "${dockerd_start}" -fi +retry_count=0 +docker_ok="false" + +until [ "${docker_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; +do + # Start using sudo if not invoked as root + if [ "$(id -u)" -ne 0 ]; then + sudo /bin/sh -c "${dockerd_start}" + else + eval "${dockerd_start}" + fi + + set +e + docker info > /dev/null 2>&1 && docker_ok="true" + + if [ "${docker_ok}" != "true" ]; then + echo "(*) Failed to start docker, retrying in 5s..." + retry_count=`expr $retry_count + 1` + sleep 5s + fi + set -e +done set +e diff --git a/test/docker-in-docker/docker_retry.sh b/test/docker-in-docker/docker_retry.sh new file mode 100644 index 000000000..cc35fbb04 --- /dev/null +++ b/test/docker-in-docker/docker_retry.sh @@ -0,0 +1,32 @@ +#!/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-ps" docker ps + +sleep 5s + +# Stop docker +pkill dockerd +pkill containerd + +sleep 5s + +set +e + docker_ok_code="$(docker info > /dev/null 2>&1; echo $?)" +set -e + +check "docker-not-running" bash -c "[[ ${docker_ok_code} == 1 ]]" + +# Testing retry logic +./test-scripts/docker-test-init.sh + +check "docker-started-after-retries" docker ps + +# Report result +reportResults diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index 9c44478f1..525ce6930 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -67,6 +67,11 @@ } }, "remoteUser": "node" + }, + "docker_retry": { + "image": "ubuntu:focal", + "features": { + "docker-in-docker": {} + } } - } diff --git a/test/docker-in-docker/test-scripts/docker-test-init.sh b/test/docker-in-docker/test-scripts/docker-test-init.sh new file mode 100644 index 000000000..b213fb40c --- /dev/null +++ b/test/docker-in-docker/test-scripts/docker-test-init.sh @@ -0,0 +1,26 @@ +#!/bin/sh +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +retry_count=0 +docker_ok="false" + +until [ "${docker_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; +do + if [ "${retry_count}" -eq "3" ]; then + echo "Starting docker after 3 retries..." + /usr/local/share/docker-init.sh + fi + + set +e + docker info > /dev/null 2>&1 && docker_ok="true" + + if [ "${docker_ok}" != "true" ]; then + echo "(*) Failed to start docker, retrying in 5s... Retry count: ${retry_count}" + retry_count=`expr $retry_count + 1` + sleep 1s + fi + set -e +done \ No newline at end of file From f2594c5cc85e18450a31865f8387c8dc064006f2 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Mon, 7 Aug 2023 16:29:54 -0700 Subject: [PATCH 035/361] Automated documentation update (#642) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/desktop-lite/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop-lite/README.md b/src/desktop-lite/README.md index 48397929b..7094a2425 100644 --- a/src/desktop-lite/README.md +++ b/src/desktop-lite/README.md @@ -48,7 +48,7 @@ You can also connect to the desktop using a [VNC viewer](https://www.realvnc.com ## Customizing Fluxbox -The window manager is installed is [Fluxbox](http://fluxbox.org/). **Right-click** to see the application menu. In addition, any UI-based commands you execute inside the dev container will automatically appear on the desktop. +The window manager installed is [Fluxbox](http://fluxbox.org/). **Right-click** to see the application menu. In addition, any UI-based commands you execute inside the dev container will automatically appear on the desktop. You can customize the desktop using Fluxbox configuration files. The configuration files are located in the `.fluxbox` folder of the home directory of the user you using to connect to the dev container (`$HOME/.fluxbox`). From 9e75db9a1dbcc245f94707c0917470143c17595b Mon Sep 17 00:00:00 2001 From: Shikanime Deva Date: Thu, 10 Aug 2023 01:20:27 +0200 Subject: [PATCH 036/361] Avoid unexpected shell expansion (#645) * Avoid unexpected shell expansion * Add install Node with ZSH as default test * Fix LTS only work on >bionic * Bump Node feature version * Remove huge universal image on scenarios --- src/node/devcontainer-feature.json | 4 ++-- src/node/install.sh | 24 ++++++++++++------------ test/node/scenarios.json | 20 +++++++++++--------- test/node/zsh_default.sh | 12 ++++++++++++ 4 files changed, 37 insertions(+), 23 deletions(-) create mode 100755 test/node/zsh_default.sh diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 9bf90b67f..80bd91275 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "1.2.0", + "version": "1.2.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.", @@ -49,4 +49,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 9048ac22e..1ce8fee3f 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -9,7 +9,7 @@ export NODE_VERSION="${VERSION:-"lts"}" export NVM_VERSION="${NVMVERSION:-"0.39.2"}" -export NVM_DIR=${NVMINSTALLPATH:-"/usr/local/share/nvm"} +export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}" INSTALL_TOOLS_FOR_NODE_GYP="${NODEGYPDEPENDENCIES:-true}" # Comma-separated list of node versions to be installed (with nvm) @@ -118,10 +118,10 @@ set -e umask 0002 # Do not update profile - we'll do this manually export PROFILE=/dev/null -curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash -source ${NVM_DIR}/nvm.sh +curl -so- "https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh" | bash +source "${NVM_DIR}/nvm.sh" if [ "${NODE_VERSION}" != "" ]; then - nvm alias default ${NODE_VERSION} + nvm alias default "${NODE_VERSION}" fi EOF )" @@ -149,9 +149,9 @@ usermod -a -G nvm ${USERNAME} umask 0002 if [ ! -d "${NVM_DIR}" ]; then # Create nvm dir, and set sticky bit - mkdir -p ${NVM_DIR} - chown "${USERNAME}:nvm" ${NVM_DIR} - chmod g+rws ${NVM_DIR} + mkdir -p "${NVM_DIR}" + chown "${USERNAME}:nvm" "${NVM_DIR}" + chmod g+rws "${NVM_DIR}" su ${USERNAME} -c "${nvm_install_snippet}" 2>&1 # Update rc files if [ "${UPDATE_RC}" = "true" ]; then @@ -160,11 +160,11 @@ if [ ! -d "${NVM_DIR}" ]; then else echo "NVM already installed." if [ "${NODE_VERSION}" != "" ]; then - su ${USERNAME} -c "umask 0002 && . $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm alias default ${NODE_VERSION}" + su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm install '${NODE_VERSION}' && nvm alias default '${NODE_VERSION}'" fi fi -# Additional node versions to be installed but not be set as +# Additional node versions to be installed but not be set as # default we can assume the nvm is the group owner of the nvm # directory and the sticky bit on directories so any installed # files will have will have the correct ownership (nvm) @@ -173,12 +173,12 @@ if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then IFS="," read -a additional_versions <<< "$ADDITIONAL_VERSIONS" for ver in "${additional_versions[@]}"; do - su ${USERNAME} -c "umask 0002 && . $NVM_DIR/nvm.sh && nvm install ${ver}" + su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm install '${ver}'" done # Ensure $NODE_VERSION is on the $PATH if [ "${NODE_VERSION}" != "" ]; then - su ${USERNAME} -c "umask 0002 && . $NVM_DIR/nvm.sh && nvm use default" + su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm use default" fi IFS=$OLDIFS fi @@ -218,7 +218,7 @@ fi # Clean up -su ${USERNAME} -c "umask 0002 && . $NVM_DIR/nvm.sh && nvm clear-cache" +su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm clear-cache" rm -rf /var/lib/apt/lists/* # Ensure privs are correct for installed node versions. Unfortunately the diff --git a/test/node/scenarios.json b/test/node/scenarios.json index 6112a6ea4..5524f5c52 100644 --- a/test/node/scenarios.json +++ b/test/node/scenarios.json @@ -17,21 +17,23 @@ } } }, - "version_none": { - "image": "mcr.microsoft.com/devcontainers/base", - "remoteUser": "vscode", + "zsh_default": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "node": { - "version": "none" + "version": "lts" + }, + "common-utils": { + "configureZshAsDefaultShell": true } } }, - "install_node_on_universal_image": { - "image": "mcr.microsoft.com/devcontainers/universal", - "remoteUser": "codespace", + "version_none": { + "image": "mcr.microsoft.com/devcontainers/base", + "remoteUser": "vscode", "features": { "node": { - "version": "19.1.0" + "version": "none" } } }, @@ -43,4 +45,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/node/zsh_default.sh b/test/node/zsh_default.sh new file mode 100755 index 000000000..c7c9e57f4 --- /dev/null +++ b/test/node/zsh_default.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" + +# Report result +reportResults From be082b0ef5f0f3795708cd1a6c37a427d8c0bb73 Mon Sep 17 00:00:00 2001 From: naturedamends <120284608+naturedamends@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:01:57 +0100 Subject: [PATCH 037/361] Common utils: Add config to remove zsh rc files from (#614) * Add config to remove zsh rc files from common-utils. * Bump version and add config to install script. * Checks preventing excess writing to .zshrc. * Change devcontainer feature option name. * Coding standards. * Favour adding feature via config and default to overriding .zshrc with dev-container default template. * Update devcontainer-feature.json * Update devcontainer-feature.json * Update src/common-utils/devcontainer-feature.json Co-authored-by: Samruddhi Khandale * Update src/common-utils/devcontainer-feature.json Co-authored-by: Samruddhi Khandale * Testing for using devcontainer .zshrc template file. * Coding standards. * Coding standards. * Update configure_zsh_as_default_shell_no_template.sh * Grammar in configure_zsh_as_default_shell.sh * Testing accounts for marked file cache (#4) * Account for mark file in testing. * Remove some debugging and tests back * Add back tests? * Update configure_zsh_no_template.sh --------- Co-authored-by: Samruddhi Khandale --- src/common-utils/devcontainer-feature.json | 7 ++- src/common-utils/install.sh | 1 + src/common-utils/main.sh | 51 +++++++++++-------- .../configure_zsh_as_default_shell.sh | 2 + .../common-utils/configure_zsh_no_template.sh | 15 ++++++ test/common-utils/scenarios.json | 11 ++++ 6 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 test/common-utils/configure_zsh_no_template.sh diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index a924aa77a..d70848278 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.0.11", + "version": "2.1.0", "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.", @@ -20,6 +20,11 @@ "default": true, "description": "Install Oh My Zsh!?" }, + "installOhMyZshConfig": { + "type": "boolean", + "default": true, + "description": "Allow installing the default dev container .zshrc templates?" + }, "upgradePackages": { "type": "boolean", "default": true, diff --git a/src/common-utils/install.sh b/src/common-utils/install.sh index d08a2f2c1..8f1ece4d7 100755 --- a/src/common-utils/install.sh +++ b/src/common-utils/install.sh @@ -12,6 +12,7 @@ set -e INSTALL_ZSH="${INSTALLZSH:-"true"}" CONFIGURE_ZSH_AS_DEFAULT_SHELL="${CONFIGUREZSHASDEFAULTSHELL:-"false"}" INSTALL_OH_MY_ZSH="${INSTALLOHMYZSH:-"true"}" +INSTALL_OH_MY_ZSH_CONFIG="${INSTALLOHMYZSHCONFIG:-"true"}" UPGRADE_PACKAGES="${UPGRADEPACKAGES:-"true"}" USERNAME="${USERNAME:-"automatic"}" USER_UID="${UID:-"automatic"}" diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index 6d5ac0719..d3f7ef267 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -12,6 +12,7 @@ set -e INSTALL_ZSH="${INSTALLZSH:-"true"}" CONFIGURE_ZSH_AS_DEFAULT_SHELL="${CONFIGUREZSHASDEFAULTSHELL:-"false"}" INSTALL_OH_MY_ZSH="${INSTALLOHMYZSH:-"true"}" +INSTALL_OH_MY_ZSH_CONFIG="${INSTALLOHMYZSHCONFIG:-"true"}" UPGRADE_PACKAGES="${UPGRADEPACKAGES:-"true"}" USERNAME="${USERNAME:-"automatic"}" USER_UID="${USERUID:-"automatic"}" @@ -390,7 +391,8 @@ else fi # Restore user .bashrc / .profile / .zshrc defaults from skeleton file if it doesn't exist or is empty -possible_rc_files=( ".bashrc" ".profile" ".zshrc" ) +possible_rc_files=( ".bashrc" ".profile" ) +[ "$INSTALL_OH_MY_ZSH_CONFIG" == "true" ] && possible_rc_files+=('.zshrc') for rc_file in "${possible_rc_files[@]}"; do if [ -f "/etc/skel/${rc_file}" ]; then if [ ! -e "${user_home}/${rc_file}" ] || [ ! -s "${user_home}/${rc_file}" ]; then @@ -450,33 +452,42 @@ if [ "${INSTALL_ZSH}" = "true" ]; then # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. - oh_my_install_dir="${user_home}/.oh-my-zsh" - if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MY_ZSH}" = "true" ]; then - template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + if [ "${INSTALL_OH_MY_ZSH}" = "true" ]; then user_rc_file="${user_home}/.zshrc" - umask g-w,o-w - mkdir -p ${oh_my_install_dir} - 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/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 - echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} - sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="devcontainers"/g' ${user_rc_file} + oh_my_install_dir="${user_home}/.oh-my-zsh" + template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + if [ ! -d "${oh_my_install_dir}" ]; then + umask g-w,o-w + mkdir -p ${oh_my_install_dir} + 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/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 + + # Shrink git while still enabling updates + cd "${oh_my_install_dir}" + git repack -a -d -f --depth=1 --window=1 + fi # Add Dev Containers theme mkdir -p ${oh_my_install_dir}/custom/themes cp -f "${FEATURE_DIR}/scripts/devcontainers.zsh-theme" "${oh_my_install_dir}/custom/themes/devcontainers.zsh-theme" - ln -s "${oh_my_install_dir}/custom/themes/devcontainers.zsh-theme" "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + ln -sf "${oh_my_install_dir}/custom/themes/devcontainers.zsh-theme" "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + + # Add devcontainer .zshrc template + if [ "$INSTALL_OH_MY_ZSH_CONFIG" = "true" ]; then + echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="devcontainers"/g' ${user_rc_file} + fi - # Shrink git while still enabling updates - cd "${oh_my_install_dir}" - git repack -a -d -f --depth=1 --window=1 # Copy to non-root user if one is specified if [ "${USERNAME}" != "root" ]; then - cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root + copy_to_user_files=("${oh_my_install_dir}") + [ -f "$user_rc_file" ] && copy_to_user_files+=("$user_rc_file") + cp -rf "${copy_to_user_files[@]}" /root chown -R ${USERNAME}:${group_name} "${oh_my_install_dir}" "${user_rc_file}" fi fi diff --git a/test/common-utils/configure_zsh_as_default_shell.sh b/test/common-utils/configure_zsh_as_default_shell.sh index e5cc89e51..6a569a990 100644 --- a/test/common-utils/configure_zsh_as_default_shell.sh +++ b/test/common-utils/configure_zsh_as_default_shell.sh @@ -7,6 +7,8 @@ source dev-container-features-test-lib # Definition specific tests check "default-shell-is-zsh" bash -c "getent passwd $(whoami) | awk -F: '{ print $7 }' | grep '/bin/zsh'" +# check it overrides the ~/.zshrc with default dev containers template +check "default-zshrc-is-dev-container-template" bash -c "cat ~/.zshrc | grep ZSH_THEME | grep devcontainers" # Report result reportResults diff --git a/test/common-utils/configure_zsh_no_template.sh b/test/common-utils/configure_zsh_no_template.sh new file mode 100644 index 000000000..c62cae967 --- /dev/null +++ b/test/common-utils/configure_zsh_no_template.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +function file_not_overridden() { + cat ~/.zshrc | grep 'alias fnomockalias=' | grep testingmock +} +check "default-zsh-with-no-zshrc" file_not_overridden + +# Report result +reportResults diff --git a/test/common-utils/scenarios.json b/test/common-utils/scenarios.json index 9800242e9..904a47bac 100644 --- a/test/common-utils/scenarios.json +++ b/test/common-utils/scenarios.json @@ -115,6 +115,17 @@ } } }, + "configure_zsh_no_template": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "postCreateCommand": "echo alias fnomockalias=testingmock >> /root/.zshrc", + "remoteUser": "root", + "features": { + "common-utils": { + "installZsh": true, + "installOhMyZshConfig": false + } + } + }, "config-subdirectory": { "image": "alpine", "remoteUser": "devcontainer", From 8b6f08825643e2d5835ed3138dee5301a4db4ffa Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Fri, 11 Aug 2023 16:17:50 -0700 Subject: [PATCH 038/361] Automated documentation update (#650) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/common-utils/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common-utils/README.md b/src/common-utils/README.md index 1eddcbcc3..b8e37a8c8 100644 --- a/src/common-utils/README.md +++ b/src/common-utils/README.md @@ -18,6 +18,7 @@ Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-r | installZsh | Install ZSH? | boolean | true | | configureZshAsDefaultShell | Change default shell to ZSH? | boolean | false | | installOhMyZsh | Install Oh My Zsh!? | boolean | true | +| installOhMyZshConfig | Allow installing the default dev container .zshrc templates? | boolean | true | | upgradePackages | Upgrade OS packages? | boolean | true | | username | Enter name of a non-root user to configure or none to skip | string | automatic | | userUid | Enter UID for non-root user | string | automatic | From f90cb26c7f15659f3e2be8061295997df2bb76cc Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Mon, 14 Aug 2023 16:12:47 -0700 Subject: [PATCH 039/361] Common-utils: Fixes "No package jq available" errors for CentOS (#644) * add checks for "jq" * install epel-release if necessary * clean up epel-release post installation of "jq" --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 18 +++++++++++++++--- test/common-utils/centos-7.sh | 1 + test/common-utils/fedora.sh | 1 + test/common-utils/mariner.sh | 1 + test/common-utils/rocky-8.sh | 1 + test/common-utils/rocky-9.sh | 1 + 7 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index d70848278..4eaef2128 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.1.0", + "version": "2.1.1", "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 d3f7ef267..3731994de 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -175,6 +175,11 @@ install_redhat_packages() { man-db \ strace" + local install_cmd=dnf + if ! type dnf > /dev/null 2>&1; then + install_cmd=yum + 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 @@ -201,16 +206,23 @@ install_redhat_packages() { package_list="${package_list} zsh" fi - local install_cmd=dnf - if ! type dnf > /dev/null 2>&1; then - install_cmd=yum + # Install EPEL repository if needed (required to install 'jq' for CentOS) + local remove_epel="false" + if ! ${install_cmd} -q list jq >/dev/null 2>&1; then + ${install_cmd} -y install epel-release + remove_epel="true" fi + ${install_cmd} -y install ${package_list} # Get to latest versions of all packages if [ "${UPGRADE_PACKAGES}" = "true" ]; then ${install_cmd} upgrade -y fi + + if [[ "${remove_epel}" = "true" ]]; then + ${install_cmd} -y remove epel-release + fi } # Alpine Linux packages diff --git a/test/common-utils/centos-7.sh b/test/common-utils/centos-7.sh index a81ae2f3e..69f01d1ed 100755 --- a/test/common-utils/centos-7.sh +++ b/test/common-utils/centos-7.sh @@ -9,6 +9,7 @@ source dev-container-features-test-lib . /etc/os-release check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${VERSION_ID}" = "7" +check "jq" jq --version # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/fedora.sh b/test/common-utils/fedora.sh index b32a7f112..67706d49a 100755 --- a/test/common-utils/fedora.sh +++ b/test/common-utils/fedora.sh @@ -9,6 +9,7 @@ source dev-container-features-test-lib . /etc/os-release check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${ID}" = "fedora" +check "jq" jq --version # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/mariner.sh b/test/common-utils/mariner.sh index ab6cb6e13..b0d2c91e4 100755 --- a/test/common-utils/mariner.sh +++ b/test/common-utils/mariner.sh @@ -9,6 +9,7 @@ source dev-container-features-test-lib . /etc/os-release check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${ID}" = "mariner" +check "jq" jq --version # Report result reportResults \ No newline at end of file diff --git a/test/common-utils/rocky-8.sh b/test/common-utils/rocky-8.sh index be1223b3c..d08731824 100755 --- a/test/common-utils/rocky-8.sh +++ b/test/common-utils/rocky-8.sh @@ -10,6 +10,7 @@ source dev-container-features-test-lib 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 \ No newline at end of file diff --git a/test/common-utils/rocky-9.sh b/test/common-utils/rocky-9.sh index 452c1935f..cb2b339e1 100755 --- a/test/common-utils/rocky-9.sh +++ b/test/common-utils/rocky-9.sh @@ -10,6 +10,7 @@ source dev-container-features-test-lib check "non-root user" test "$(whoami)" = "devcontainer" check "distro" test "${PLATFORM_ID}" = "platform:el9" check "curl" curl --version +check "jq" jq --version # Report result reportResults \ No newline at end of file From 1d1a23625dc714629ab863cbca7e890516d4283d Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 16 Aug 2023 11:38:35 -0700 Subject: [PATCH 040/361] Ruby Feature [bug fix] : Fails if the dev container has the same ruby version installed (#609) * Ruby Feature [bug fix] : Fails if the dev container has the same ruby version installed * add comment * remove scenario --- src/ruby/devcontainer-feature.json | 2 +- src/ruby/install.sh | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ruby/devcontainer-feature.json b/src/ruby/devcontainer-feature.json index fdf8b26b3..29591e6a6 100644 --- a/src/ruby/devcontainer-feature.json +++ b/src/ruby/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "ruby", - "version": "1.1.0", + "version": "1.1.1", "name": "Ruby (via rvm)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/ruby", "description": "Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies.", diff --git a/src/ruby/install.sh b/src/ruby/install.sh index 087108520..2b47d8a27 100755 --- a/src/ruby/install.sh +++ b/src/ruby/install.sh @@ -177,9 +177,11 @@ fi find_version_from_git_tags RUBY_VERSION "https://github.com/ruby/ruby" "tags/v" "_" # Just install Ruby if RVM already installed -if [ $(rvm --version) != "" ]; then +if rvm --version > /dev/null; then echo "Ruby Version Manager already exists." - if [ "${RUBY_VERSION}" != "none" ]; then + 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}" fi @@ -191,6 +193,9 @@ else # Determine appropriate settings for rvm installer 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" @@ -273,8 +278,11 @@ if [ "${SKIP_RBENV_RBUILD}" != "true" ]; then ln -s /usr/local/share/ruby-build /home/${USERNAME}/.rbenv/plugins/ruby-build fi - ln -s /usr/local/rvm/rubies/default/bin/ruby /usr/local/rvm/gems/default/bin - + # 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 + 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 From c58b614202d525b2530d0bbfc45a6a6bbc1a9569 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 16 Aug 2023 11:38:49 -0700 Subject: [PATCH 041/361] Azure-cli: Revert temporary fix which installed az with python (#629) --- src/azure-cli/devcontainer-feature.json | 4 ++-- src/azure-cli/install.sh | 1 - test/azure-cli/install_with_python.sh | 14 ++++++++++++++ test/azure-cli/scenarios.json | 10 ++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 test/azure-cli/install_with_python.sh diff --git a/src/azure-cli/devcontainer-feature.json b/src/azure-cli/devcontainer-feature.json index 09c295c21..3d1c0dfa3 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.0", + "version": "1.2.1", "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.", @@ -26,7 +26,7 @@ "installUsingPython": { "type": "boolean", "description": "Install Azure CLI using Python instead of pipx", - "default": true + "default": false } }, "customizations": { diff --git a/src/azure-cli/install.sh b/src/azure-cli/install.sh index 9b0d72db3..51759488d 100755 --- a/src/azure-cli/install.sh +++ b/src/azure-cli/install.sh @@ -141,7 +141,6 @@ install_using_pip_strategy() { ver="==${AZ_VERSION}" fi - # Temprary quick fix for https://github.com/devcontainers/features/issues/624 if [ "${INSTALL_USING_PYTHON}" = "true" ]; then install_with_complete_python_installation "${ver}" || install_with_pipx "${ver}" || return 1 else diff --git a/test/azure-cli/install_with_python.sh b/test/azure-cli/install_with_python.sh new file mode 100644 index 000000000..4848ac55f --- /dev/null +++ b/test/azure-cli/install_with_python.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 \ No newline at end of file diff --git a/test/azure-cli/scenarios.json b/test/azure-cli/scenarios.json index 231345225..b7a732395 100644 --- a/test/azure-cli/scenarios.json +++ b/test/azure-cli/scenarios.json @@ -28,5 +28,15 @@ "installBicep": true } } + }, + "install_with_python": { + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "user": "vscode", + "features": { + "azure-cli": { + "version": "latest", + "installUsingPython": true + } + } } } \ No newline at end of file From 9fd5daf06b40794a067c4cdbb8e258d99eeefc24 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Wed, 16 Aug 2023 11:43:06 -0700 Subject: [PATCH 042/361] Automated documentation update (#656) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/azure-cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/README.md b/src/azure-cli/README.md index c3e23db8a..2e7b6d39b 100644 --- a/src/azure-cli/README.md +++ b/src/azure-cli/README.md @@ -18,7 +18,7 @@ Installs the Azure CLI along with needed dependencies. Useful for base Dockerfil | version | Select or enter an Azure CLI version. (Available versions may vary by Linux distribution.) | string | latest | | extensions | Optional comma separated list of Azure CLI extensions to install in profile. | string | - | | installBicep | Optionally install Azure Bicep | boolean | false | -| installUsingPython | Install Azure CLI using Python instead of pipx | boolean | true | +| installUsingPython | Install Azure CLI using Python instead of pipx | boolean | false | ## Customizations From a4b31f314fa1385950a9dbc81682d9acb2e2fba9 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Fri, 18 Aug 2023 16:01:50 -0700 Subject: [PATCH 043/361] [docker-in-docker] Reverts the retry logic (#659) * Update install.sh * [docker-in-docker] Update retry logic * revert the retry logic! --- .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 30 ++++------------- test/docker-in-docker/docker_retry.sh | 32 ------------------- test/docker-in-docker/scenarios.json | 6 ---- .../test-scripts/docker-test-init.sh | 26 --------------- 5 files changed, 7 insertions(+), 89 deletions(-) delete mode 100644 test/docker-in-docker/docker_retry.sh delete mode 100644 test/docker-in-docker/test-scripts/docker-test-init.sh diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index b5e0aeea6..2afdd1142 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.3.0", + "version": "2.3.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 f7e5a7d52..bdef12697 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -423,30 +423,12 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU INNEREOF )" -retry_count=0 -docker_ok="false" - -until [ "${docker_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; -do - # Start using sudo if not invoked as root - if [ "$(id -u)" -ne 0 ]; then - sudo /bin/sh -c "${dockerd_start}" - else - eval "${dockerd_start}" - fi - - set +e - docker info > /dev/null 2>&1 && docker_ok="true" - - if [ "${docker_ok}" != "true" ]; then - echo "(*) Failed to start docker, retrying in 5s..." - retry_count=`expr $retry_count + 1` - sleep 5s - fi - set -e -done - -set +e +# Start using sudo if not invoked as root +if [ "$(id -u)" -ne 0 ]; then + sudo /bin/sh -c "${dockerd_start}" +else + eval "${dockerd_start}" +fi # Execute whatever commands were passed in (if any). This allows us # to set this script to ENTRYPOINT while still executing the default CMD. diff --git a/test/docker-in-docker/docker_retry.sh b/test/docker-in-docker/docker_retry.sh deleted file mode 100644 index cc35fbb04..000000000 --- a/test/docker-in-docker/docker_retry.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/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-ps" docker ps - -sleep 5s - -# Stop docker -pkill dockerd -pkill containerd - -sleep 5s - -set +e - docker_ok_code="$(docker info > /dev/null 2>&1; echo $?)" -set -e - -check "docker-not-running" bash -c "[[ ${docker_ok_code} == 1 ]]" - -# Testing retry logic -./test-scripts/docker-test-init.sh - -check "docker-started-after-retries" docker ps - -# Report result -reportResults diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index 525ce6930..de4e7d6bc 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -67,11 +67,5 @@ } }, "remoteUser": "node" - }, - "docker_retry": { - "image": "ubuntu:focal", - "features": { - "docker-in-docker": {} - } } } diff --git a/test/docker-in-docker/test-scripts/docker-test-init.sh b/test/docker-in-docker/test-scripts/docker-test-init.sh deleted file mode 100644 index b213fb40c..000000000 --- a/test/docker-in-docker/test-scripts/docker-test-init.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- - -retry_count=0 -docker_ok="false" - -until [ "${docker_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; -do - if [ "${retry_count}" -eq "3" ]; then - echo "Starting docker after 3 retries..." - /usr/local/share/docker-init.sh - fi - - set +e - docker info > /dev/null 2>&1 && docker_ok="true" - - if [ "${docker_ok}" != "true" ]; then - echo "(*) Failed to start docker, retrying in 5s... Retry count: ${retry_count}" - retry_count=`expr $retry_count + 1` - sleep 1s - fi - set -e -done \ No newline at end of file From 71ac2021e1350e673c0602b2ab6526d4353bc293 Mon Sep 17 00:00:00 2001 From: Bulat Khusnimardanov <61210700+bulat-khusnimardanov@users.noreply.github.com> Date: Tue, 22 Aug 2023 02:31:20 +0200 Subject: [PATCH 044/361] Enhancing Terraform Feature Stability (#657) * Added fallback method to retrieve cosign version * Update src/terraform/install.sh Co-authored-by: Samruddhi Khandale * Remove get_cosign_latest_version function --------- Co-authored-by: Samruddhi Khandale --- src/terraform/devcontainer-feature.json | 2 +- src/terraform/install.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/terraform/devcontainer-feature.json b/src/terraform/devcontainer-feature.json index b1e6df2c8..119b4335e 100644 --- a/src/terraform/devcontainer-feature.json +++ b/src/terraform/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "terraform", - "version": "1.3.3", + "version": "1.3.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 82e3e8d51..1156f6d41 100755 --- a/src/terraform/install.sh +++ b/src/terraform/install.sh @@ -165,7 +165,8 @@ ensure_cosign() { if ! type cosign > /dev/null 2>&1; then echo "Installing cosign..." - local LATEST_COSIGN_VERSION=$(curl https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d : -f2 | tr -d "v\", ") + LATEST_COSIGN_VERSION="latest" + find_version_from_git_tags LATEST_COSIGN_VERSION 'https://github.com/sigstore/cosign' curl -L "https://github.com/sigstore/cosign/releases/latest/download/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb" -o /tmp/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb dpkg -i /tmp/cosign_${LATEST_COSIGN_VERSION}_${architecture}.deb From a08434f2b18627f31107a4ef6ab4f578a92aeaec Mon Sep 17 00:00:00 2001 From: nyuyuyu <127820811+nyuyuyu@users.noreply.github.com> Date: Fri, 25 Aug 2023 01:38:40 +0900 Subject: [PATCH 045/361] [Rust] Remove `mutantdino.resourcemonitor` extension (#665) * Remove `mutantdino.resourcemonitor` extension * Bump version * Revert "Bump version" This reverts commit e9c514911dd6de6b03e235536e8602f62cbe5b99. * Bump version --- src/rust/devcontainer-feature.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rust/devcontainer-feature.json b/src/rust/devcontainer-feature.json index bc9044c60..70ed39eab 100644 --- a/src/rust/devcontainer-feature.json +++ b/src/rust/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "rust", - "version": "1.0.12", + "version": "1.1.0", "name": "Rust", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/rust", "description": "Installs Rust, common Rust utilities, and their required dependencies", @@ -39,7 +39,6 @@ "vscode": { "extensions": [ "vadimcn.vscode-lldb", - "mutantdino.resourcemonitor", "rust-lang.rust-analyzer", "tamasfe.even-better-toml", "serayuzgur.crates" From 0cf690ee14279d9e7ae5e1308950d6067d3aba81 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Thu, 24 Aug 2023 12:31:40 -0700 Subject: [PATCH 046/361] [docker in docker] Adds retries for docker daemon startup and cgroup nesting (#669) * adds retry logic * adds stress test workflow * Adds cgroup retries * update workflow * move sleep cmd * update test * cleanup --- .../docker-in-docker-stress-test.yaml | 37 +++++++ .../devcontainer-feature.json | 2 +- src/docker-in-docker/install.sh | 102 +++++++++++++----- .../docker_with_on_create_command.sh | 18 ++++ test/docker-in-docker/scenarios.json | 9 ++ test/docker-in-docker/test.sh | 2 +- 6 files changed, 139 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/docker-in-docker-stress-test.yaml create mode 100755 test/docker-in-docker/docker_with_on_create_command.sh diff --git a/.github/workflows/docker-in-docker-stress-test.yaml b/.github/workflows/docker-in-docker-stress-test.yaml new file mode 100644 index 000000000..569f94699 --- /dev/null +++ b/.github/workflows/docker-in-docker-stress-test.yaml @@ -0,0 +1,37 @@ +name: "Stress test - Docker in Docker" +on: + pull_request: + paths: + - 'src/docker-in-docker/**' + workflow_dispatch: + +jobs: + test: + strategy: + matrix: + test-pass: [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50 ] + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - 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:ubuntu . + + test-onCreate: + strategy: + matrix: + test-pass: [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50 ] + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: "Install latest devcontainer CLI" + 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 diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 2afdd1142..edfaa1f09 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.3.1", + "version": "2.4.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 bdef12697..a0d4a943c 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -367,7 +367,7 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU find /run /var/run -iname 'docker*.pid' -delete || : find /run /var/run -iname 'container*.pid' -delete || : - ## Dind wrapper script from docker team, adapted to a function + # -- Start: dind wrapper script -- # Maintained: https://github.com/moby/moby/blob/master/hack/dind export container=docker @@ -384,31 +384,52 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU mount -t tmpfs none /tmp fi - # cgroup v2: enable nesting - if [ -f /sys/fs/cgroup/cgroup.controllers ]; then - # move the processes from the root group to the /init group, - # otherwise writing subtree_control fails with EBUSY. - # An error during moving non-existent process (i.e., "cat") is ignored. - mkdir -p /sys/fs/cgroup/init - xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || : - # enable controllers - sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ - > /sys/fs/cgroup/cgroup.subtree_control - fi - ## Dind wrapper over. + set_cgroup_nesting() + { + # cgroup v2: enable nesting + if [ -f /sys/fs/cgroup/cgroup.controllers ]; then + # move the processes from the root group to the /init group, + # otherwise writing subtree_control fails with EBUSY. + # An error during moving non-existent process (i.e., "cat") is ignored. + mkdir -p /sys/fs/cgroup/init + xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || : + # enable controllers + sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ + > /sys/fs/cgroup/cgroup.subtree_control + fi + } + + # Set cgroup nesting, retrying if necessary + retry_cgroup_nesting=0 + + until [ "${retry_cgroup_nesting}" -eq "5" ]; + do + set +e + set_cgroup_nesting + + if [ $? -ne 0 ]; then + echo "(*) cgroup v2: Failed to enable nesting, retrying..." + else + break + fi + + retry_cgroup_nesting=`expr $retry_cgroup_nesting + 1` + set -e + done + + # -- End: dind wrapper script -- # Handle DNS set +e - cat /etc/resolv.conf | grep -i 'internal.cloudapp.net' - if [ $? -eq 0 ] && [ "${AZURE_DNS_AUTO_DETECTION}" = "true" ] - then - echo "Setting dockerd Azure DNS." - CUSTOMDNS="--dns 168.63.129.16" - else - echo "Not setting dockerd DNS manually." - CUSTOMDNS="" - fi - + cat /etc/resolv.conf | grep -i 'internal.cloudapp.net' > /dev/null 2>&1 + if [ $? -eq 0 ] && [ "${AZURE_DNS_AUTO_DETECTION}" = "true" ] + then + echo "Setting dockerd Azure DNS." + CUSTOMDNS="--dns 168.63.129.16" + else + echo "Not setting dockerd DNS manually." + CUSTOMDNS="" + fi set -e if [ -z "$DOCKER_DEFAULT_ADDRESS_POOL" ] @@ -423,12 +444,35 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU INNEREOF )" -# Start using sudo if not invoked as root -if [ "$(id -u)" -ne 0 ]; then - sudo /bin/sh -c "${dockerd_start}" -else - eval "${dockerd_start}" -fi +retry_docker_start_count=0 +docker_ok="false" + +until [ "${docker_ok}" = "true" ] || [ "${retry_docker_start_count}" -eq "5" ]; +do + # Start using sudo if not invoked as root + if [ "$(id -u)" -ne 0 ]; then + sudo /bin/sh -c "${dockerd_start}" + else + eval "${dockerd_start}" + fi + + retry_count=0 + until [ "${docker_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; + do + sleep 1s + set +e + docker info > /dev/null 2>&1 && docker_ok="true" + set -e + + retry_count=`expr $retry_count + 1` + done + + if [ "${docker_ok}" != "true" ]; then + echo "(*) Failed to start docker, retrying..." + fi + + retry_docker_start_count=`expr $retry_docker_start_count + 1` +done # Execute whatever commands were passed in (if any). This allows us # to set this script to ENTRYPOINT while still executing the default CMD. diff --git a/test/docker-in-docker/docker_with_on_create_command.sh b/test/docker-in-docker/docker_with_on_create_command.sh new file mode 100755 index 000000000..131304acb --- /dev/null +++ b/test/docker-in-docker/docker_with_on_create_command.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "version" docker --version +check "docker-ps" bash -c "docker ps" + +check "docker-init-exists" bash -c "ls /usr/local/share/docker-init.sh" +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 \ No newline at end of file diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index de4e7d6bc..1274877b9 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -67,5 +67,14 @@ } }, "remoteUser": "node" + }, + // DO NOT REMOVE: This scenario is used by the docker-in-docker-stress-test workflow + "docker_with_on_create_command": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": {} + }, + "remoteUser": "vscode", + "onCreateCommand": "docker ps && sleep 5s && docker ps" } } diff --git a/test/docker-in-docker/test.sh b/test/docker-in-docker/test.sh index 1eebfd243..e86a841c2 100755 --- a/test/docker-in-docker/test.sh +++ b/test/docker-in-docker/test.sh @@ -8,10 +8,10 @@ source dev-container-features-test-lib # Feature specific tests check "version" docker --version check "docker-init-exists" bash -c "ls /usr/local/share/docker-init.sh" +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'" -check "docker-ps" bash -c "docker ps" # Report result reportResults \ No newline at end of file From 0668db5edea56a0d57575db127b793f59b489e7b Mon Sep 17 00:00:00 2001 From: naturedamends <120284608+naturedamends@users.noreply.github.com> Date: Sat, 26 Aug 2023 00:39:32 +0100 Subject: [PATCH 047/361] Commonutils: chown: cannot access '/home/vscode/.zshrc': No such file or directory (#662) * Update main.sh * Update devcontainer-feature.json * Test install as non root user. --------- Co-authored-by: Samruddhi Khandale --- src/common-utils/devcontainer-feature.json | 2 +- src/common-utils/main.sh | 2 +- .../configure_zsh_no_template_first_step.sh | 12 ++++++++++++ ... => configure_zsh_no_template_second_step.sh} | 5 ++--- test/common-utils/scenarios.json | 16 +++++++++++++--- 5 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 test/common-utils/configure_zsh_no_template_first_step.sh rename test/common-utils/{configure_zsh_no_template.sh => configure_zsh_no_template_second_step.sh} (57%) diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index 4eaef2128..747be3074 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "common-utils", - "version": "2.1.1", + "version": "2.1.2", "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 3731994de..902f9e694 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -500,7 +500,7 @@ if [ "${INSTALL_ZSH}" = "true" ]; then copy_to_user_files=("${oh_my_install_dir}") [ -f "$user_rc_file" ] && copy_to_user_files+=("$user_rc_file") cp -rf "${copy_to_user_files[@]}" /root - chown -R ${USERNAME}:${group_name} "${oh_my_install_dir}" "${user_rc_file}" + chown -R ${USERNAME}:${group_name} "${copy_to_user_files[@]}" fi fi fi diff --git a/test/common-utils/configure_zsh_no_template_first_step.sh b/test/common-utils/configure_zsh_no_template_first_step.sh new file mode 100644 index 000000000..d26874d3a --- /dev/null +++ b/test/common-utils/configure_zsh_no_template_first_step.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "default-zsh-with-no-zshrc" bash -c "[ ! -e ~/.zshrc ]" + +# Report result +reportResults diff --git a/test/common-utils/configure_zsh_no_template.sh b/test/common-utils/configure_zsh_no_template_second_step.sh similarity index 57% rename from test/common-utils/configure_zsh_no_template.sh rename to test/common-utils/configure_zsh_no_template_second_step.sh index c62cae967..734fa66e9 100644 --- a/test/common-utils/configure_zsh_no_template.sh +++ b/test/common-utils/configure_zsh_no_template_second_step.sh @@ -4,12 +4,11 @@ set -e # Optional: Import test library source dev-container-features-test-lib - # Definition specific tests function file_not_overridden() { - cat ~/.zshrc | grep 'alias fnomockalias=' | grep testingmock + cat $1 | grep 'alias fnomockalias=' | grep testingmock } -check "default-zsh-with-no-zshrc" file_not_overridden +check "default-zsh-with-no-zshrc" file_not_overridden /home/devcontainer/.zshrc # Report result reportResults diff --git a/test/common-utils/scenarios.json b/test/common-utils/scenarios.json index 904a47bac..d7b296d41 100644 --- a/test/common-utils/scenarios.json +++ b/test/common-utils/scenarios.json @@ -115,10 +115,20 @@ } } }, - "configure_zsh_no_template": { + "configure_zsh_no_template_second_step": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "postCreateCommand": "echo alias fnomockalias=testingmock >> /root/.zshrc", - "remoteUser": "root", + "postCreateCommand": "echo alias fnomockalias=testingmock >> /home/devcontainer/.zshrc", + "remoteUser": "devcontainer", + "features": { + "common-utils": { + "installZsh": true, + "installOhMyZshConfig": false + } + } + }, + "configure_zsh_no_template_first_step": { + "image": "debian:bullseye", + "remoteUser": "devcontainer", "features": { "common-utils": { "installZsh": true, From 95a169b9ef92c9dae454df5e87d4c26d56a863c9 Mon Sep 17 00:00:00 2001 From: eitsupi <50911393+eitsupi@users.noreply.github.com> Date: Tue, 29 Aug 2023 06:26:30 +0900 Subject: [PATCH 048/361] [node] Install the latest version of nvm by default (#673) feat(node): install the latest nvm by default --- src/node/devcontainer-feature.json | 10 ++++--- src/node/install.sh | 42 +++++++++++++++++++++++++++++- test/node/install_nvm_0.39.sh | 14 ++++++++++ test/node/scenarios.json | 10 ++++++- 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100755 test/node/install_nvm_0.39.sh diff --git a/src/node/devcontainer-feature.json b/src/node/devcontainer-feature.json index 80bd91275..aee55a1f6 100644 --- a/src/node/devcontainer-feature.json +++ b/src/node/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "node", - "version": "1.2.1", + "version": "1.3.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.", @@ -30,7 +30,11 @@ }, "nvmVersion": { "type": "string", - "default": "0.39.2", + "proposals": [ + "latest", + "0.39" + ], + "default": "latest", "description": "Version of NVM to install." } }, @@ -49,4 +53,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 1ce8fee3f..809109413 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -8,7 +8,7 @@ # Maintainer: The Dev Container spec maintainers export NODE_VERSION="${VERSION:-"lts"}" -export NVM_VERSION="${NVMVERSION:-"0.39.2"}" +export NVM_VERSION="${NVMVERSION:-"latest"}" export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}" INSTALL_TOOLS_FOR_NODE_GYP="${NODEGYPDEPENDENCIES:-true}" @@ -78,6 +78,40 @@ check_packages() { 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}" +} + # Ensure apt is in non-interactive to avoid prompts export DEBIAN_FRONTEND=noninteractive @@ -92,6 +126,10 @@ fi # Install dependencies check_packages apt-transport-https curl ca-certificates tar gnupg2 dirmngr +if ! type git > /dev/null 2>&1; then + check_packages git +fi + # Install yarn if type yarn > /dev/null 2>&1; then echo "Yarn already installed." @@ -112,6 +150,8 @@ elif [ "${NODE_VERSION}" = "latest" ]; then export NODE_VERSION="node" fi +find_version_from_git_tags NVM_VERSION "https://github.com/nvm-sh/nvm" + # Install snipppet that we will run as the user nvm_install_snippet="$(cat << EOF set -e diff --git a/test/node/install_nvm_0.39.sh b/test/node/install_nvm_0.39.sh new file mode 100755 index 000000000..6b96381b5 --- /dev/null +++ b/test/node/install_nvm_0.39.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "version" bash -c "node --version" +check "pnpm" pnpm -v +check "nvm version" bash -c ". /usr/local/share/nvm/nvm.sh && nvm --version | grep 0.39" + +# Report result +reportResults diff --git a/test/node/scenarios.json b/test/node/scenarios.json index 5524f5c52..a45620fbe 100644 --- a/test/node/scenarios.json +++ b/test/node/scenarios.json @@ -44,5 +44,13 @@ "version": "16" } } + }, + "install_nvm_0.39": { + "image": "mcr.microsoft.com/devcontainers/base", + "features": { + "node": { + "nvmVersion": "0.39" + } + } } -} \ No newline at end of file +} From 96bff0097028001e6e4126c5528d37cb8c13e785 Mon Sep 17 00:00:00 2001 From: Samruddhi Khandale Date: Mon, 28 Aug 2023 15:15:43 -0700 Subject: [PATCH 049/361] Automated documentation update (#676) Automated documentation update [skip ci] Co-authored-by: github-actions --- src/node/README.md | 2 +- src/rust/README.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node/README.md b/src/node/README.md index 7a4909600..b98391922 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -18,7 +18,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 | -| nvmVersion | Version of NVM to install. | string | 0.39.2 | +| nvmVersion | Version of NVM to install. | string | latest | ## Customizations diff --git a/src/rust/README.md b/src/rust/README.md index f72b56f82..974d78516 100644 --- a/src/rust/README.md +++ b/src/rust/README.md @@ -23,7 +23,6 @@ Installs Rust, common Rust utilities, and their required dependencies ### VS Code Extensions - `vadimcn.vscode-lldb` -- `mutantdino.resourcemonitor` - `rust-lang.rust-analyzer` - `tamasfe.even-better-toml` - `serayuzgur.crates` From 038bed3d58a84885da8a008b80905da17d57a543 Mon Sep 17 00:00:00 2001 From: eitsupi <50911393+eitsupi@users.noreply.github.com> Date: Wed, 30 Aug 2023 01:32:46 +0900 Subject: [PATCH 050/361] ci: remove `ubuntu:bionic` from test matrixes (#677) ci: remove ubuntu:bionic from test matrixes --- .github/workflows/test-all.yaml | 3 +-- .github/workflows/test-pr.yaml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-all.yaml b/.github/workflows/test-all.yaml index 8dc206d86..40f5efa75 100644 --- a/.github/workflows/test-all.yaml +++ b/.github/workflows/test-all.yaml @@ -42,7 +42,6 @@ jobs: ] baseImage: [ - "ubuntu:bionic", "ubuntu:focal", "ubuntu:jammy", "debian:11", @@ -112,4 +111,4 @@ jobs: run: npm install -g @devcontainers/cli - name: "Testing global scenarios" - run: devcontainer features test --global-scenarios-only . \ No newline at end of file + run: devcontainer features test --global-scenarios-only . diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 6b91b612d..d60733574 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -49,7 +49,6 @@ jobs: features: ${{ fromJSON(needs.detect-changes.outputs.features) }} baseImage: [ - "ubuntu:bionic", "ubuntu:focal", "ubuntu:jammy", "debian:11", @@ -80,4 +79,4 @@ jobs: run: npm install -g @devcontainers/cli - name: "Testing '${{ matrix.features }}' scenarios" - run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . \ No newline at end of file + run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . From 96c1eea40fc97b471ba0b33fcc79273c7ce586c7 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 11 Sep 2023 20:16:24 +0200 Subject: [PATCH 051/361] Use dotnet-install.sh in .NET feature (#628) * Use dotnet-install.sh in .NET feature * Use latest.version files * Cleanup runtime args * Use latest.version files in tests as well * Improve tests, remove code duplication * Add stderr helper * Validate version inputs * Use suggested description Co-authored-by: Samruddhi Khandale * Shorter version description Co-authored-by: Samruddhi Khandale * Shorter version description Co-authored-by: Samruddhi Khandale * Clean up apt lists * Verify 7.0 is latest * Fix PATH, add test for .NET global tools * Include a copy of dotnet-install.sh in the Feature * Configure useful env variables * Use stringly typed booleans * Keep imperative writing style in option hints * Update maintainers Co-authored-by: Samruddhi Khandale * Move dotnet-install.sh into a vendor directory * Refactor variables * Amend * Amend 2 * Use default options from devcontainer-feature.json * Add back variables * Fix shellchek warning in fetch_latest_sdk_version * Inline install_version function * Fix ShellCheck warnings * Improve CSV parsing * Default to latest when configuring an empty version * Add support for runtime-only configurations * Move 'none' check higher up * Deduplicate helper functions, sort into files * Address the user more directly in NOTES * Remove unnecessary defaults * Replace feature -> Feature Co-authored-by: Samruddhi Khandale * Add update-dotnet-install-script workflow * Apply suggestions from code review Co-authored-by: Samruddhi Khandale * Don't skip ci for automated script update --------- Co-authored-by: Samruddhi Khandale --- .../update-dotnet-install-script.yml | 48 + src/dotnet/NOTES.md | 67 + src/dotnet/devcontainer-feature.json | 41 +- src/dotnet/install.sh | 506 +---- src/dotnet/scripts/dotnet-helpers.sh | 119 ++ .../scripts/fetch-latest-dotnet-install.sh | 16 + src/dotnet/scripts/string-helpers.sh | 42 + src/dotnet/scripts/vendor/README.md | 27 + src/dotnet/scripts/vendor/dotnet-install.sh | 1746 +++++++++++++++++ test/dotnet/dotnet_env.sh | 4 + test/dotnet/dotnet_helpers.sh | 71 + test/dotnet/install_additional_dotnet.sh | 17 - .../dotnet/install_aspnetcore_runtime_only.sh | 27 + test/dotnet/install_dotnet_3.sh | 17 - test/dotnet/install_dotnet_6_bullseye.sh | 17 - test/dotnet/install_dotnet_6_focal.sh | 17 - test/dotnet/install_dotnet_6_jammy.sh | 17 - test/dotnet/install_dotnet_7_bullseye.sh | 17 - test/dotnet/install_dotnet_7_jammy.sh | 17 - test/dotnet/install_dotnet_exact_version.sh | 24 + test/dotnet/install_dotnet_global_tool.sh | 25 + test/dotnet/install_dotnet_latest.sh | 17 - ...all_dotnet_latest_when_version_is_empty.sh | 30 + test/dotnet/install_dotnet_lts.sh | 27 +- .../install_dotnet_multiple_versions.sh | 30 + test/dotnet/install_dotnet_runtime_only.sh | 27 + .../dotnet/install_dotnet_specific_release.sh | 26 + ...otnet_specific_release_and_feature_band.sh | 24 + test/dotnet/install_wo_apt.sh | 17 - test/dotnet/projects/.gitignore | 2 + test/dotnet/projects/multitargeting/Class1.cs | 4 + .../multitargeting/example_classlib.csproj | 9 + test/dotnet/projects/net5.0/Program.cs | 32 + .../projects/net5.0/example_project.csproj | 13 + test/dotnet/projects/net6.0/Program.cs | 30 + .../projects/net6.0/example_project.csproj | 14 + test/dotnet/projects/net7.0/Program.cs | 30 + .../projects/net7.0/example_project.csproj | 14 + test/dotnet/projects/net8.0/Program.cs | 32 + .../projects/net8.0/example_project.csproj | 14 + test/dotnet/projects/netcoreapp3.1/Program.cs | 39 + .../netcoreapp3.1/example_project.csproj | 14 + test/dotnet/scenarios.json | 83 +- test/dotnet/test.sh | 31 +- 44 files changed, 2771 insertions(+), 670 deletions(-) create mode 100644 .github/workflows/update-dotnet-install-script.yml create mode 100644 src/dotnet/scripts/dotnet-helpers.sh create mode 100755 src/dotnet/scripts/fetch-latest-dotnet-install.sh create mode 100644 src/dotnet/scripts/string-helpers.sh create mode 100644 src/dotnet/scripts/vendor/README.md create mode 100755 src/dotnet/scripts/vendor/dotnet-install.sh create mode 100644 test/dotnet/dotnet_env.sh create mode 100644 test/dotnet/dotnet_helpers.sh delete mode 100644 test/dotnet/install_additional_dotnet.sh create mode 100644 test/dotnet/install_aspnetcore_runtime_only.sh delete mode 100644 test/dotnet/install_dotnet_3.sh delete mode 100644 test/dotnet/install_dotnet_6_bullseye.sh delete mode 100644 test/dotnet/install_dotnet_6_focal.sh delete mode 100644 test/dotnet/install_dotnet_6_jammy.sh delete mode 100644 test/dotnet/install_dotnet_7_bullseye.sh delete mode 100644 test/dotnet/install_dotnet_7_jammy.sh create mode 100644 test/dotnet/install_dotnet_exact_version.sh create mode 100644 test/dotnet/install_dotnet_global_tool.sh delete mode 100644 test/dotnet/install_dotnet_latest.sh create mode 100644 test/dotnet/install_dotnet_latest_when_version_is_empty.sh create mode 100644 test/dotnet/install_dotnet_multiple_versions.sh create mode 100644 test/dotnet/install_dotnet_runtime_only.sh create mode 100644 test/dotnet/install_dotnet_specific_release.sh create mode 100644 test/dotnet/install_dotnet_specific_release_and_feature_band.sh delete mode 100644 test/dotnet/install_wo_apt.sh create mode 100644 test/dotnet/projects/.gitignore create mode 100644 test/dotnet/projects/multitargeting/Class1.cs create mode 100644 test/dotnet/projects/multitargeting/example_classlib.csproj create mode 100644 test/dotnet/projects/net5.0/Program.cs create mode 100644 test/dotnet/projects/net5.0/example_project.csproj create mode 100644 test/dotnet/projects/net6.0/Program.cs create mode 100644 test/dotnet/projects/net6.0/example_project.csproj create mode 100644 test/dotnet/projects/net7.0/Program.cs create mode 100644 test/dotnet/projects/net7.0/example_project.csproj create mode 100644 test/dotnet/projects/net8.0/Program.cs create mode 100644 test/dotnet/projects/net8.0/example_project.csproj create mode 100644 test/dotnet/projects/netcoreapp3.1/Program.cs create mode 100644 test/dotnet/projects/netcoreapp3.1/example_project.csproj mode change 100755 => 100644 test/dotnet/test.sh diff --git a/.github/workflows/update-dotnet-install-script.yml b/.github/workflows/update-dotnet-install-script.yml new file mode 100644 index 000000000..56adf854d --- /dev/null +++ b/.github/workflows/update-dotnet-install-script.yml @@ -0,0 +1,48 @@ +name: "Updates vendor 'dotnet-install' script" +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' # Runs every Sunday at midnight UTC (adjust as needed) + +jobs: + fetch-latest-dotnet-install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Run fetch-latest-dotnet-install.sh + run: src/dotnet/scripts/fetch-latest-dotnet-install.sh + + - name: Create a PR for dotnet-install.sh + id: push_image_info + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + run: | + set -e + echo "Start." + + # Configure git and Push updates + git config --global user.email github-actions@github.com + git config --global user.name github-actions + git config pull.rebase false + + branch=automated-script-update-$GITHUB_RUN_ID + git checkout -b $branch + message='[Updates] Automated vendor 'dotnet-install' script' + + # Add / update and commit + git add src/dotnet/scripts/vendor/dotnet-install.sh + git commit -m 'Automated dotnet-install script update' || export NO_UPDATES=true + + # Push + if [ "$NO_UPDATES" != "true" ] ; then + git push origin "$branch" + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${GITHUB_REPOSITORY}/pulls \ + -f title="$message" \ + -f body="$message" \ + -f head="$branch" \ + -f base="$GITHUB_REF_NAME" + fi diff --git a/src/dotnet/NOTES.md b/src/dotnet/NOTES.md index 19fe92f31..578aceaf3 100644 --- a/src/dotnet/NOTES.md +++ b/src/dotnet/NOTES.md @@ -1,4 +1,71 @@ +## Configuration examples +Installing only the latest .NET SDK version (the default). + +``` json +{ +"features": { + "ghcr.io/devcontainers/features/dotnet:2": "latest" // or "" or {} +} +``` + +Installing an additional SDK version. Multiple versions can be specified as comma-separated values. + +``` json +{ +"features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "additionalVersions": "lts" + } +} +``` + +Installing specific SDK versions. + +``` json +{ +"features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "6.0", + "additionalVersions": "7.0, 8.0" + } +} +``` + +Installing a specific SDK feature band. + +``` json +{ +"features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "6.0.4xx", + } +} +``` + +Installing a specific SDK patch version. + +``` json +{ +"features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "6.0.412", + } +} +``` + +Installing only the .NET Runtime or the ASP.NET Core Runtime. (The SDK includes all runtimes so this configuration is only useful if you need to run .NET apps without building them from source.) + +``` json +{ +"features": { + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "none", + "dotnetRuntimeVersions": "latest, lts", + "aspnetCoreRuntimeVersions": "latest, lts", + } +} +``` ## OS Support diff --git a/src/dotnet/devcontainer-feature.json b/src/dotnet/devcontainer-feature.json index 4f484ccba..f00e0d3ea 100644 --- a/src/dotnet/devcontainer-feature.json +++ b/src/dotnet/devcontainer-feature.json @@ -1,35 +1,44 @@ { "id": "dotnet", - "version": "1.1.4", + "version": "2.0.0", "name": "Dotnet CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/dotnet", - "description": "Installs the .NET CLI. Provides option of installing sdk or runtime, and option of versions to install. Uses latest version of .NET sdk as defaults to install.", + "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.", "options": { "version": { "type": "string", "proposals": [ "latest", - "7", - "6", - "3.1" + "lts", + "none", + "8.0", + "7.0", + "6.0" ], "default": "latest", - "description": "Select or enter a dotnet CLI version. (Available versions may vary by Linux distribution.)" + "description": "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." }, - "runtimeOnly": { - "type": "boolean", - "default": false, - "description": "Install just the dotnet runtime if true, and sdk if false." + "additionalVersions": { + "type": "string", + "default": "", + "description": "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." + }, + "dotnetRuntimeVersions": { + "type": "string", + "default": "", + "description": "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." }, - "installUsingApt": { - "type": "boolean", - "default": true, - "description": "If true, it installs using apt instead of the release URL" + "aspNetCoreRuntimeVersions": { + "type": "string", + "default": "", + "description": "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." } }, "containerEnv": { - "DOTNET_ROOT": "/usr/local/dotnet/current", - "PATH": "/usr/local/dotnet/current:${PATH}" + "DOTNET_ROOT": "/usr/share/dotnet", + "PATH": "$PATH:$DOTNET_ROOT:~/.dotnet/tools", + "DOTNET_RUNNING_IN_CONTAINER": "true", + "DOTNET_USE_POLLING_FILE_WATCHER": "true" }, "customizations": { "vscode": { diff --git a/src/dotnet/install.sh b/src/dotnet/install.sh index 178994d71..ff9244d3a 100644 --- a/src/dotnet/install.sh +++ b/src/dotnet/install.sh @@ -4,46 +4,20 @@ # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- # -# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/dotnet.md -# Maintainer: The VS Code and Codespaces Teams - +# Docs: https://github.com/devcontainers/features/tree/main/src/dotnet +# Maintainer: The Dev Container spec maintainers DOTNET_VERSION="${VERSION:-"latest"}" -DOTNET_RUNTIME_ONLY="${RUNTIMEONLY:-"false"}" -OVERRIDE_DEFAULT_VERSION="${OVERRIDEDEFAULTVERSION:-"true"}" -INSTALL_USING_APT="${INSTALLUSINGAPT:-"true"}" - -DOTNET_LATEST="7" -DOTNET_LTS="6" - -USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" -UPDATE_RC="${UPDATE_RC:-"true"}" -TARGET_DOTNET_ROOT="${TARGET_DOTNET_ROOT:-"/usr/local/dotnet"}" -ACCESS_GROUP="${ACCESS_GROUP:-"dotnet"}" - -MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" -DOTNET_ARCHIVE_ARCHITECTURES="amd64" -DOTNET_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal hirsute jammy" -# Feed URI sourced from the official dotnet-install.sh -# https://github.com/dotnet/install-scripts/blob/1b98b94a6f6d81cc4845eb88e0195fac67caa0a6/src/dotnet-install.sh#L1342-L1343 -DOTNET_CDN_FEED_URI="https://dotnetcli.azureedge.net" -# Ubuntu 22.04 and on do not ship with libssl1.1, which is required for versions of .NET < 6.0 -DOTNET_VERSION_CODENAMES_REQUIRE_OLDER_LIBSSL_1="buster bullseye bionic focal hirsute" +ADDITIONAL_VERSIONS="${ADDITIONALVERSIONS}" +DOTNET_RUNTIME_VERSIONS="${DOTNETRUNTIMEVERSIONS}" +ASPNETCORE_RUNTIME_VERSIONS="${ASPNETCORERUNTIMEVERSIONS}" -# Comma-separated list of dotnet versions to be installed -# alongside DOTNET_VERSION, but not set as default. -ADDITIONAL_VERSIONS=${ADDITIONALVERSIONS:-""} - -APT_PREFERENCES_CONTENT="$(cat << 'EOF' -Package: * -Pin: origin "packages.microsoft.com" -Pin-Priority: 1001 -EOF -)" +set -e -APT_PREFERENCES_FILE_ADDED="false" -APT_PREFERENCES_UPDATED="false" +# Import trim_whitespace and split_csv +source "scripts/string-helpers.sh" -set -e +# Import install_sdk and install_runtime +source "scripts/dotnet-helpers.sh" # Clean up rm -rf /var/lib/apt/lists/* @@ -53,72 +27,14 @@ err() { echo "(!) $*" >&2 } -# Ensure the appropriate root user is running the script. -if [ "$(id -u)" -ne 0 ]; then - err 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' - exit 1 -fi - -# Ensure that login shells get the correct path if the user updated the PATH using ENV. -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)") - 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 - -################### -# Helper Functions -################### - -# Cleanup temporary directory and associated files when exiting the script. -cleanup() { - EXIT_CODE=$? - set +e - if [[ -n "${TMP_DIR}" ]]; then - echo "Executing cleanup of tmp files" - rm -Rf "${TMP_DIR}" - fi - exit $EXIT_CODE -} -trap cleanup EXIT - -# Add dotnet directory to PATH in bashrc/zshrc files if OVERRIDE_DEFAULT_VERSION=true. -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 - fi -} - -apt_get_update() -{ +apt_get_update() { if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then echo "Running apt-get update..." apt-get update -y fi } -# Check if packages are installed and installs them if not. +# Checks if packages are installed and installs them if not check_packages() { if ! dpkg -s "$@" > /dev/null 2>&1; then apt_get_update @@ -126,367 +42,77 @@ check_packages() { fi } -# Get appropriate architecture name for .NET binaries for the target OS -get_architecture_name_for_target_os() { - local architecture - architecture="$(uname -m)" - case $architecture in - x86_64) architecture="x64";; - aarch64 | armv8*) architecture="arm64";; - *) err "Architecture ${architecture} unsupported"; exit 1 ;; - esac - - echo "${architecture}" -} - -# Soft version matching that resolves a version for a given package in the *current apt-cache* -# Return value is stored in first argument (the unprocessed version) -apt_cache_package_and_version_soft_match() { - # Version - local version_variable_name="$1" - local requested_version=${!version_variable_name} - # Package Name - local package_variable_name="$2" - local partial_package_name=${!package_variable_name} - local package_name - # Exit on no match? - local exit_on_no_match="${3:-true}" - local major_minor_version - - # Ensure we've exported useful variables - . /etc/os-release - local architecture="$(dpkg --print-architecture)" - - apt-get update -y - major_minor_version="$(echo "${requested_version}" | cut -d "." --field=1,2)" - package_name="$(apt-cache search "${partial_package_name}-[0-9].[0-9]$" | awk -F" - " '{print $1}' | grep -m 1 "${partial_package_name}-${major_minor_version}")" - dot_escaped="${requested_version//./\\.}" - dot_plus_escaped="${dot_escaped//+/\\+}" - # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/ - version_regex="^(.+:)?${dot_plus_escaped}([\\.\\+ ~:-]|$)" - set +e # Don't exit if finding version fails - handle gracefully - fuzzy_version="$(apt-cache madison ${package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${version_regex}")" - set -e - if [ -z "${fuzzy_version}" ]; then - echo "(!) No full or partial for package \"${partial_package_name}\" (resolved: \"${package_name}\") match found in apt-cache for \"${requested_version}\" on OS ${ID} ${VERSION_CODENAME} (${architecture})." - - if $exit_on_no_match; then - echo "Available versions:" - apt-cache madison ${package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+' - exit 1 # Fail entire script - else - echo "Continuing to fallback method if available" - return 1; - fi - fi - - # Globally assign fuzzy_version to this value - # Use this value as the return value of this function - declare -g ${version_variable_name}="=${fuzzy_version}" - echo "${version_variable_name} ${!version_variable_name}" - - # Globally assign package to this value - # Use this value as the return value of this function - declare -g ${package_variable_name}="${package_name}" - echo "${package_variable_name} ${!package_variable_name}" -} - -# Install .NET CLI using apt-get package installer -install_using_apt() { - local sdk_or_runtime="$1" - local target_dotnet_version="$2" - local use_msft_repo="$3" - - if [ "${use_msft_repo}" = "true" ]; then - # Install dependencies - check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr - # Import key safely and import Microsoft apt repo - 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/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list - - [ ! -f /etc/apt/preferences ] && APT_PREFERENCES_FILE_ADDED="true" - APT_PREFERENCES_UPDATED="true" - - # See https://github.com/dotnet/core/issues/7699 - echo "${APT_PREFERENCES_CONTENT}" >> /etc/apt/preferences - apt-get update -y - fi - - # .NET 7 is not a LTS version, so handle latest and LTS versions differently - export APT_DOTNET_VERSION="$target_dotnet_version" - if [ "${APT_DOTNET_VERSION}" = "latest" ]; then - APT_DOTNET_VERSION="${DOTNET_LATEST}.0" - elif [ "${APT_DOTNET_VERSION}" = "lts" ]; then - APT_DOTNET_VERSION="${DOTNET_LTS}.0" - fi - - # Sets target_dotnet_version and dotnet_package if matches found. - local base_dotnet_package="dotnet-${sdk_or_runtime}" - export DOTNET_PACKAGE="${base_dotnet_package}" - apt_cache_package_and_version_soft_match APT_DOTNET_VERSION DOTNET_PACKAGE false - if [ "$?" != 0 ] || [ ${DOTNET_PACKAGE} == "${base_dotnet_package}" ]; then - echo "Failed to find requested version." - return 1 - fi - - if type dotnet > /dev/null 2>&1 && [[ "$(dotnet --version)" == *"${APT_DOTNET_VERSION}"* ]] ; then - echo "dotnet version ${APT_DOTNET_VERSION} is already installed" - return 1 - fi - - echo "Installing '${DOTNET_PACKAGE}${APT_DOTNET_VERSION}'..." - apt-get install -yq ${DOTNET_PACKAGE}${APT_DOTNET_VERSION} - if [ "$?" != 0 ]; then - echo "Failed to complete apt install of ${DOTNET_PACKAGE}${TARGET_DOTNET_VERSION}" - return 1 - fi - - # Add symlink for current - CURRENT_DIR="${TARGET_DOTNET_ROOT}/current" - if [[ -d "${CURRENT_DIR}" ]]; then - rm -rf "${CURRENT_DIR}" - fi - mkdir -p "${TARGET_DOTNET_ROOT}" - local dotnet_installed_version="$(dotnet --version)" - # See if its the distro version - if [[ "$(dotnet --info)" == *"Base Path: /usr/lib/dotnet/${DOTNET_SDK_OR_RUNTIME}/${dotnet_installed_version}"* ]]; then - ln -s "/usr/lib/dotnet" "${CURRENT_DIR}" - else - # Location used by MS repo versions - ln -s "/usr/share/dotnet" "${CURRENT_DIR}" - fi -} - -# Find and extract .NET binary download details based on user-requested version -# args: -# sdk_or_runtime $1 -# dotnet_version_to_download $2 -# exports: -# DOTNET_DOWNLOAD_URL -# DOTNET_DOWNLOAD_HASH -# DOTNET_DOWNLOAD_NAME -get_full_version_details() { - local sdk_or_runtime="$1" - local architecture - local dotnet_channel_version - local dotnet_releases_url - local dotnet_releases_json - local dotnet_latest_version - local dotnet_download_details - - export DOTNET_DOWNLOAD_VERSION="$2" - export DOTNET_DOWNLOAD_URL - export DOTNET_DOWNLOAD_HASH - export DOTNET_DOWNLOAD_NAME - - # Set architecture variable to current user's architecture (x64 or ARM64). - architecture="$(get_architecture_name_for_target_os)" - - # Set VERSION to empty string to ensure jq includes all .NET versions in reverse sort below - if [ "${DOTNET_DOWNLOAD_VERSION}" = "latest" ]; then - DOTNET_DOWNLOAD_VERSION="" - fi - - dotnet_patchless_version="$(echo "${DOTNET_DOWNLOAD_VERSION}" | cut -d "." --field=1,2)" - - set +e - dotnet_channel_version="$(curl -s "${DOTNET_CDN_FEED_URI}/dotnet/release-metadata/releases-index.json" | jq -r --arg channel_version "${dotnet_patchless_version}" '[."releases-index"[]] | sort_by(."channel-version") | reverse | map( select(."channel-version" | startswith($channel_version))) | first | ."channel-version"')" - set -e +if [ "$(id -u)" -ne 0 ]; then + err 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi - # Construct the releases URL using the official channel-version if one was found. Otherwise make a best-effort using the user input. - if [ -n "${dotnet_channel_version}" ] && [ "${dotnet_channel_version}" != "null" ]; then - dotnet_releases_url="${DOTNET_CDN_FEED_URI}/dotnet/release-metadata/${dotnet_channel_version}/releases.json" - else - dotnet_releases_url="${DOTNET_CDN_FEED_URI}/dotnet/release-metadata/${dotnet_patchless_version}/releases.json" - fi +# For our own convenience, combine DOTNET_VERSION and ADDITIONAL_VERSIONS into a single 'versions' array +versions=() - set +e - dotnet_releases_json="$(curl -s "${dotnet_releases_url}")" - set -e - - if [ -n "${dotnet_releases_json}" ] && [[ ! "${dotnet_releases_json}" = *"Error"* ]]; then - dotnet_latest_version="$(echo "${dotnet_releases_json}" | jq -r --arg sdk_or_runtime "${sdk_or_runtime}" '."latest-\($sdk_or_runtime)"')" - # If user-specified version has 2 or more dots, use it as is. Otherwise use latest version. - if [ "$(echo "${DOTNET_DOWNLOAD_VERSION}" | grep -o "\." | wc -l)" -lt "2" ]; then - DOTNET_DOWNLOAD_VERSION="${dotnet_latest_version}" - fi +# The version can be set to 'none' for runtime-only installations, then the array will just remain empty +# Ensure there are no leading or trailing spaces that can break regex pattern matching +if [ "$DOTNET_VERSION" != "none" ]; then + versions+=("$(trim_whitespace "$DOTNET_VERSION")") + for additional_version in $(split_csv "$ADDITIONAL_VERSIONS"); do + versions+=("$additional_version") + done +fi - dotnet_download_details="$(echo "${dotnet_releases_json}" | jq -r --arg sdk_or_runtime "${sdk_or_runtime}" --arg dotnet_version "${DOTNET_DOWNLOAD_VERSION}" --arg arch "${architecture}" '.releases[]."\($sdk_or_runtime)" | select(.version==$dotnet_version) | .files[] | select(.name=="dotnet-\($sdk_or_runtime)-linux-\($arch).tar.gz")')" - if [ -n "${dotnet_download_details}" ]; then - echo "Found .NET binary version ${DOTNET_DOWNLOAD_VERSION}" - DOTNET_DOWNLOAD_URL="$(echo "${dotnet_download_details}" | jq -r '.url')" - DOTNET_DOWNLOAD_HASH="$(echo "${dotnet_download_details}" | jq -r '.hash')" - DOTNET_DOWNLOAD_NAME="$(echo "${dotnet_download_details}" | jq -r '.name')" - else - err "Unable to find .NET binary for version ${DOTNET_DOWNLOAD_VERSION}" - exit 1 - fi - else - err "Unable to find .NET release details for version ${DOTNET_DOWNLOAD_VERSION} at ${dotnet_releases_url}" +dotnetRuntimeVersions=() +for dotnetRuntimeVersion in $(split_csv "$DOTNET_RUNTIME_VERSIONS"); do + dotnetRuntimeVersions+=("$dotnetRuntimeVersion") +done + +aspNetCoreRuntimeVersions=() +for aspNetCoreRuntimeVersion in $(split_csv "$ASPNETCORE_RUNTIME_VERSIONS"); do + aspNetCoreRuntimeVersions+=("$aspNetCoreRuntimeVersion") +done + +# Fail fast in case of bad input to avoid unneccesary work +# v1 of the .NET feature allowed specifying only a major version 'X' like '3' +# v2 removed this ability +# - because install-dotnet.sh does not support it directly +# - because the previous behavior installed an old version like '3.0.103', not the newest version '3.1.426', which was counterintuitive +for version in "${versions[@]}"; do + if [[ "$version" =~ ^[0-9]+$ ]]; then + err "Unsupported .NET SDK version '${version}'. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version." exit 1 fi -} - -# Install .NET CLI using the .NET releases url -install_using_dotnet_releases_url() { - local sdk_or_runtime="$1" - local version="$2" - - # Check listed package dependencies and install them if they are not already installed. - # NOTE: icu-devtools is a small package with similar dependencies to .NET. - # It will install the appropriate dependencies based on the OS: - # - libgcc-s1 OR libgcc1 depending on OS - # - the latest libicuXX depending on OS (eg libicu57 for stretch) - # - also installs libc6 and libstdc++6 which are required by .NET - check_packages curl ca-certificates tar jq icu-devtools libgssapi-krb5-2 zlib1g - - # Starting with Ubuntu 22.04 (jammy), libssl1.1 does not ship with the OS anymore. - if [[ "${DOTNET_VERSION_CODENAMES_REQUIRE_OLDER_LIBSSL_1}" = *"${VERSION_CODENAME}"* ]]; then - check_packages libssl1.1 - else - check_packages libssl3 - fi +done - get_full_version_details "${sdk_or_runtime}" "${version}" - - DOTNET_INSTALL_PATH="${TARGET_DOTNET_ROOT}/${DOTNET_DOWNLOAD_VERSION}" - if [ -d "${DOTNET_INSTALL_PATH}" ]; then - echo "(!) Dotnet version ${DOTNET_DOWNLOAD_VERSION} already exists." +for version in "${dotnetRuntimeVersions[@]}"; do + if [[ "$version" =~ ^[0-9]+$ ]]; then + err "Unsupported .NET Runtime version '${version}'. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version." exit 1 fi - # exports DOTNET_DOWNLOAD_URL, DOTNET_DOWNLOAD_HASH, DOTNET_DOWNLOAD_NAME - echo "DOWNLOAD LINK: ${DOTNET_DOWNLOAD_URL}" - - # Setup the access group and add the user to it. - umask 0002 - if ! cat /etc/group | grep -e "^${ACCESS_GROUP}:" > /dev/null 2>&1; then - groupadd -r "${ACCESS_GROUP}" - fi - usermod -a -G "${ACCESS_GROUP}" "${USERNAME}" - - # Download the .NET binaries. - echo "DOWNLOADING BINARY..." - TMP_DIR="/tmp/dotnetinstall" - mkdir -p "${TMP_DIR}" - curl -sSL "${DOTNET_DOWNLOAD_URL}" -o "${TMP_DIR}/${DOTNET_DOWNLOAD_NAME}" - - # Get checksum from .NET CLI blob storage using the runtime version and - # run validation (sha512sum) of checksum against the expected checksum hash. - echo "VERIFY CHECKSUM" - cd "${TMP_DIR}" - echo "${DOTNET_DOWNLOAD_HASH} *${DOTNET_DOWNLOAD_NAME}" | sha512sum -c - - - # Extract binaries and add to path. - mkdir -p "${DOTNET_INSTALL_PATH}" - echo "Extract Binary to ${DOTNET_INSTALL_PATH}" - tar -xzf "${TMP_DIR}/${DOTNET_DOWNLOAD_NAME}" -C "${DOTNET_INSTALL_PATH}" --strip-components=1 +done - CURRENT_DIR="${TARGET_DOTNET_ROOT}/current" - if [[ ! -d "${CURRENT_DIR}" ]]; then - ln -s "${DOTNET_INSTALL_PATH}" "${CURRENT_DIR}" - fi - - # Give write permissions to the user. - chown -R "${USERNAME}:${USERNAME}" "${CURRENT_DIR}" - chmod g+r+w+s "${CURRENT_DIR}" - chmod -R g+r+w "${CURRENT_DIR}" - - if [[ "${OVERRIDE_DEFAULT_VERSION}" = "true" ]]; then - if [[ $(ls -l ${CURRENT_DIR}) != *"-> ${DOTNET_INSTALL_PATH}"* ]] ; then - rm "${CURRENT_DIR}" - ln -s "${DOTNET_INSTALL_PATH}" "${CURRENT_DIR}" - fi - fi - - updaterc "if [[ \"\${PATH}\" != *\"${CURRENT_DIR}\"* ]]; then export PATH=${CURRENT_DIR}:\${PATH}; fi" -} - -########################### -# Start .NET installation -########################### - -export DEBIAN_FRONTEND=noninteractive - -. /etc/os-release -architecture="$(dpkg --print-architecture)" - -# Dotnet 3.1 and 5 are not supported on Ubuntu 22.04 (jammy)+, -# due to lack of libssl3.0 support. -# See: https://github.com/microsoft/vscode-dev-containers/issues/1458#issuecomment-1135077775 -# NOTE: This will only guard against installation of the dotnet versions we propose via 'features'. -# The user can attempt to install any other version at their own risk. -if [[ "${DOTNET_VERSION}" = "3"* ]] || [[ "${DOTNET_VERSION}" = "5"* ]]; then - if [[ ! "${DOTNET_VERSION_CODENAMES_REQUIRE_OLDER_LIBSSL_1}" = *"${VERSION_CODENAME}"* ]]; then - err "Dotnet ${DOTNET_VERSION} is not supported on Ubuntu ${VERSION_CODENAME} due to a change in the 'libssl' dependency across distributions.\n Please upgrade your version of dotnet, or downgrade your OS version." +for version in "${aspNetCoreRuntimeVersions[@]}"; do + if [[ "$version" =~ ^[0-9]+$ ]]; then + err "Unsupported ASP.NET Core Runtime version '${version}'. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version." exit 1 fi -fi +done -# Determine if the user wants to download .NET Runtime only, or .NET SDK & Runtime -# and set the appropriate variables. -if [ "${DOTNET_RUNTIME_ONLY}" = "true" ]; then - DOTNET_SDK_OR_RUNTIME="runtime" -elif [ "${DOTNET_RUNTIME_ONLY}" = "false" ]; then - DOTNET_SDK_OR_RUNTIME="sdk" -else - err "Expected true for installing dotnet Runtime only or false for installing SDK and Runtime. Received ${DOTNET_RUNTIME_ONLY}." - exit 1 -fi +# Install .NET versions and dependencies +# icu-devtools includes dependencies for .NET +check_packages wget ca-certificates icu-devtools -# Install the .NET CLI -echo "(*) Installing .NET CLI..." +for version in "${versions[@]}"; do + install_sdk "$version" +done -CHANGE_OWNERSHIP="false" -if [[ "${DOTNET_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[ "${DOTNET_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]] && [[ "${INSTALL_USING_APT}" = "true" ]]; then - echo "Detected ${VERSION_CODENAME} on ${architecture}. Attempting to install dotnet from apt" +for version in "${dotnetRuntimeVersions[@]}"; do + install_runtime "dotnet" "$version" +done - install_using_apt "${DOTNET_SDK_OR_RUNTIME}" "${DOTNET_VERSION}" false || install_using_apt "${DOTNET_SDK_OR_RUNTIME}" "${DOTNET_VERSION}" true - if [ "$?" != 0 ]; then - echo "Could not install requested version from apt on current distribution." - exit 1 - fi -else - if [[ "${INSTALL_USING_APT}" = "false" ]]; then - echo "Installing dotnet from releases url" - else - echo "Attempting to install dotnet from releases url" - fi - install_using_dotnet_releases_url "${DOTNET_SDK_OR_RUNTIME}" "${DOTNET_VERSION}" - CHANGE_OWNERSHIP="true" -fi - -# Additional dotnet 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 - OVERRIDE_DEFAULT_VERSION="false" - install_using_dotnet_releases_url "${DOTNET_SDK_OR_RUNTIME}" "${version}" - done - IFS=$OLDIFS -fi - -if [ "${CHANGE_OWNERSHIP}" = "true" ]; then - if ! cat /etc/group | grep -e "^dotnet:" > /dev/null 2>&1; then - groupadd -r dotnet - fi - usermod -a -G dotnet "${USERNAME}" - - chown -R "${USERNAME}:dotnet" "${TARGET_DOTNET_ROOT}" - chmod -R g+r+w "${TARGET_DOTNET_ROOT}" - find "${TARGET_DOTNET_ROOT}" -type d -print0 | xargs -n 1 -0 chmod g+s -fi +for version in "${aspNetCoreRuntimeVersions[@]}"; do + install_runtime "aspnetcore" "$version" +done # Clean up -if [ "${APT_PREFERENCES_UPDATED}" = "true" ]; then - if [ "${APT_PREFERENCES_FILE_ADDED}" = "true" ]; then - rm -f /etc/apt/preferences - else - head -n -3 /etc/apt/preferences > /tmp/preferences-temp && mv /tmp/preferences-temp /etc/apt/preferences - fi -fi - rm -rf /var/lib/apt/lists/* +rm -rf scripts echo "Done!" diff --git a/src/dotnet/scripts/dotnet-helpers.sh b/src/dotnet/scripts/dotnet-helpers.sh new file mode 100644 index 000000000..bda0c9c3a --- /dev/null +++ b/src/dotnet/scripts/dotnet-helpers.sh @@ -0,0 +1,119 @@ +#!/bin/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/tree/main/src/dotnet +# Maintainer: The Dev Container spec maintainers +DOTNET_SCRIPTS=$(dirname "${BASH_SOURCE[0]}") +DOTNET_INSTALL_SCRIPT="$DOTNET_SCRIPTS/vendor/dotnet-install.sh" +DOTNET_INSTALL_DIR='/usr/share/dotnet' + +# 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://dotnetcli.azureedge.net/dotnet/Runtime/$channel/latest.version" + elif [ "$runtime" = "aspnetcore" ]; then + wget -qO- "https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$channel/latest.version" + else + wget -qO- "https://dotnetcli.azureedge.net/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 +} + +# Installs a version of the .NET SDK +# Usage: install_sdk +install_sdk() { + local inputVersion="$1" + local version="" + local channel="" + if [[ "$inputVersion" == "latest" ]]; then + # Fetch the latest version manually, because dotnet-install.sh does not support it directly + version=$(fetch_latest_version) + elif [[ "$inputVersion" == "lts" ]]; then + # When user input is 'lts' + # Then version=latest, channel=LTS + version="latest" + channel="LTS" + elif [[ "$inputVersion" =~ ^[0-9]+\.[0-9]+$ ]]; then + # When user input is form 'A.B' like '3.1' + # Then version=latest, channel=3.1 + version="latest" + channel="$inputVersion" + elif [[ "$inputVersion" =~ ^[0-9]+\.[0-9]+\.[0-9]xx$ ]]; then + # When user input is form 'A.B.Cxx' like '6.0.4xx' + # Then version=latest, channel=6.0.4xx + version="latest" + channel="$inputVersion" + else + # Assume version is an exact version string like '6.0.413' or '8.0.100-rc.2.23425.18' + version="$inputVersion" + fi + + # Currently this script does not make it possible to qualify the version, 'GA' is always implied + echo "Executing $DOTNET_INSTALL_SCRIPT --version $version --channel $channel --install-dir $DOTNET_INSTALL_DIR --no-path" + "$DOTNET_INSTALL_SCRIPT" \ + --version "$version" \ + --channel "$channel" \ + --install-dir "$DOTNET_INSTALL_DIR" \ + --no-path +} + +# Installs a version of the .NET Runtime +# Usage: install_runtime +install_runtime() { + local runtime="$1" + local inputVersion="$2" + local version="" + local channel="" + if [[ "$inputVersion" == "latest" ]]; then + # Fetch the latest version manually, because dotnet-install.sh does not support it directly + version=$(fetch_latest_version "$runtime") + elif [[ "$inputVersion" == "lts" ]]; then + # When user input is 'lts' + # Then version=latest, channel=LTS + version="latest" + channel="LTS" + elif [[ "$inputVersion" =~ ^[0-9]+\.[0-9]+$ ]]; then + # When user input is form 'A.B' like '3.1' + # Then version=latest, channel=3.1 + version="latest" + channel="$inputVersion" + else + # Assume version is an exact version string like '6.0.21' or '8.0.0-preview.7.23375.6' + version="$inputVersion" + fi + + echo "Executing $DOTNET_INSTALL_SCRIPT --runtime $runtime --version $version --channel $channel --install-dir $DOTNET_INSTALL_DIR --no-path" + "$DOTNET_INSTALL_SCRIPT" \ + --runtime "$runtime" \ + --version "$version" \ + --channel "$channel" \ + --install-dir "$DOTNET_INSTALL_DIR" \ + --no-path +} diff --git a/src/dotnet/scripts/fetch-latest-dotnet-install.sh b/src/dotnet/scripts/fetch-latest-dotnet-install.sh new file mode 100755 index 000000000..361d539c4 --- /dev/null +++ b/src/dotnet/scripts/fetch-latest-dotnet-install.sh @@ -0,0 +1,16 @@ +#!/bin/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/tree/main/src/dotnet +# Maintainer: The Dev Container spec maintainers +# +# Run this script to replace dotnet-install.sh with the latest and greatest available version +# +DOTNET_SCRIPTS=$(dirname "${BASH_SOURCE[0]}") +DOTNET_INSTALL_SCRIPT="$DOTNET_SCRIPTS/vendor/dotnet-install.sh" + +wget https://dot.net/v1/dotnet-install.sh -O "$DOTNET_INSTALL_SCRIPT" +chmod +x "$DOTNET_INSTALL_SCRIPT" diff --git a/src/dotnet/scripts/string-helpers.sh b/src/dotnet/scripts/string-helpers.sh new file mode 100644 index 000000000..35b4017c8 --- /dev/null +++ b/src/dotnet/scripts/string-helpers.sh @@ -0,0 +1,42 @@ +#!/bin/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/tree/main/src/dotnet +# Maintainer: The Dev Container spec maintainers + +# Removes leading and trailing whitespace from an input string +# Usage: trim_whitespace +trim_whitespace() { + text="$1" + + # Remove leading spaces + while [ "${text:0:1}" == " " ]; do + text="${text:1}" + done + + # Remove trailing spaces + while [ "${text: -1}" == " " ]; do + text="${text:0:-1}" + done + + echo "$text" +} + +# Splits comma-separated values into an array while ignoring empty entries +# Usage: split_csv +split_csv() { + local -a values=() + while IFS="," read -ra entries; do + for entry in "${entries[@]}"; do + entry="$(trim_whitespace "$entry")" + if [ -n "$entry" ]; then + values+=("$entry") + fi + done + done <<< "$1" + + echo "${values[@]}" +} \ No newline at end of file diff --git a/src/dotnet/scripts/vendor/README.md b/src/dotnet/scripts/vendor/README.md new file mode 100644 index 000000000..181b53781 --- /dev/null +++ b/src/dotnet/scripts/vendor/README.md @@ -0,0 +1,27 @@ +### **IMPORTANT NOTE** + +Scripts in this directory are sourced externally and not maintained by the Dev Container spec maintainers. Do not make changes directly as they might be overwritten at any moment. + +## dotnet-install.sh + +`dotnet-install.sh` is a copy of . ([Script reference](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script)) + +Quick options reminder for `dotnet-install.sh`: + +- `--version`: `"latest"` (default) or an exact version in the form A.B.C like `"6.0.413"` +- `--channel`: `"LTS"` (default), `"STS"`, a two-part version in the form A.B like `"6.0"` or three-part form A.B.Cxx like `"6.0.1xx"` +- `--quality`: `"daily"`, `"preview"` or `"GA"` +- The channel option is only used when version is 'latest' because an exact version overrides the channel option +- The quality option is only used when channel is 'A.B' or 'A.B.Cxx' because it can't be used with STS or LTS + +Examples + +``` +dotnet-install.sh [--version latest] [--channel LTS] +dotnet-install.sh [--version latest] --channel STS +dotnet-install.sh [--version latest] --channel 6.0 [--quality GA] +dotnet-install.sh [--version latest] --channel 6.0.4xx [--quality GA] +dotnet-install.sh [--version latest] --channel 8.0 --quality preview +dotnet-install.sh [--version latest] --channel 8.0 --quality daily +dotnet-install.sh --version 6.0.413 +``` \ No newline at end of file diff --git a/src/dotnet/scripts/vendor/dotnet-install.sh b/src/dotnet/scripts/vendor/dotnet-install.sh new file mode 100755 index 000000000..a830583cd --- /dev/null +++ b/src/dotnet/scripts/vendor/dotnet-install.sh @@ -0,0 +1,1746 @@ +#!/usr/bin/env bash +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ] && command -v tput > /dev/null; then + # see if it supports colors + ncolors=$(tput colors || echo 0) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_warning() { + printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3 +} + +say_err() { + printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, +# then and only then should the Linux distribution appear in this list. +# Adding a Linux distribution to this list does not imply distribution-specific support. +get_legacy_os_name_from_platform() { + eval $invocation + + platform="$1" + case "$platform" in + "centos.7") + echo "centos" + return 0 + ;; + "debian.8") + echo "debian" + return 0 + ;; + "debian.9") + echo "debian.9" + return 0 + ;; + "fedora.23") + echo "fedora.23" + return 0 + ;; + "fedora.24") + echo "fedora.24" + return 0 + ;; + "fedora.27") + echo "fedora.27" + return 0 + ;; + "fedora.28") + echo "fedora.28" + return 0 + ;; + "opensuse.13.2") + echo "opensuse.13.2" + return 0 + ;; + "opensuse.42.1") + echo "opensuse.42.1" + return 0 + ;; + "opensuse.42.3") + echo "opensuse.42.3" + return 0 + ;; + "rhel.7"*) + echo "rhel" + return 0 + ;; + "ubuntu.14.04") + echo "ubuntu" + return 0 + ;; + "ubuntu.16.04") + echo "ubuntu.16.04" + return 0 + ;; + "ubuntu.16.10") + echo "ubuntu.16.10" + return 0 + ;; + "ubuntu.18.04") + echo "ubuntu.18.04" + return 0 + ;; + "alpine.3.4.3") + echo "alpine" + return 0 + ;; + esac + return 1 +} + +get_legacy_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ -n "$runtime_id" ]; then + echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") + if [ -n "$os" ]; then + echo "$os" + return 0 + fi + fi + fi + + say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" + return 1 +} + +get_linux_platform_name() { + eval $invocation + + if [ -n "$runtime_id" ]; then + echo "${runtime_id%-*}" + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID${VERSION_ID:+.${VERSION_ID}}" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(&1 || true) | grep -q musl +} + +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ "$uname" = "FreeBSD" ]; then + echo "freebsd" + return 0 + elif [ "$uname" = "Linux" ]; then + local linux_platform_name="" + linux_platform_name="$(get_linux_platform_name)" || true + + if [ "$linux_platform_name" = "rhel.6" ]; then + echo $linux_platform_name + return 0 + elif is_musl_based_distro; then + echo "linux-musl" + return 0 + elif [ "$linux_platform_name" = "linux-musl" ]; then + echo "linux-musl" + return 0 + else + echo "linux" + return 0 + fi + fi + + say_err "OS name could not be detected: UName = $uname" + return 1 +} + +machine_has() { + eval $invocation + + command -v "$1" > /dev/null 2>&1 + return $? +} + +check_min_reqs() { + local hasMinimum=false + if machine_has "curl"; then + hasMinimum=true + elif machine_has "wget"; then + hasMinimum=true + fi + + if [ "$hasMinimum" = "false" ]; then + say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." + return 1 + fi + return 0 +} + +# args: +# input - $1 +to_lowercase() { + #eval $invocation + + echo "$1" | tr '[:upper:]' '[:lower:]' + return 0 +} + +# args: +# input - $1 +remove_trailing_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input%/}" + return 0 +} + +# args: +# input - $1 +remove_beginning_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input#/}" + return 0 +} + +# args: +# root_path - $1 +# child_path - $2 - this parameter can be empty +combine_paths() { + eval $invocation + + # TODO: Consider making it work with any number of paths. For now: + if [ ! -z "${3:-}" ]; then + say_err "combine_paths: Function takes two parameters." + return 1 + fi + + local root_path="$(remove_trailing_slash "$1")" + local child_path="$(remove_beginning_slash "${2:-}")" + say_verbose "combine_paths: root_path=$root_path" + say_verbose "combine_paths: child_path=$child_path" + echo "$root_path/$child_path" + return 0 +} + +get_machine_architecture() { + eval $invocation + + if command -v uname > /dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv*l) + echo "arm" + return 0 + ;; + aarch64|arm64) + echo "arm64" + return 0 + ;; + s390x) + echo "s390x" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + esac + fi + + # Always default to 'x64' + echo "x64" + return 0 +} + +# args: +# architecture - $1 +get_normalized_architecture_from_architecture() { + eval $invocation + + local architecture="$(to_lowercase "$1")" + + if [[ $architecture == \ ]]; then + echo "$(get_machine_architecture)" + return 0 + fi + + case "$architecture" in + amd64|x64) + echo "x64" + return 0 + ;; + arm) + echo "arm" + return 0 + ;; + arm64) + echo "arm64" + return 0 + ;; + s390x) + echo "s390x" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + esac + + say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" + return 1 +} + +# args: +# version - $1 +# channel - $2 +# architecture - $3 +get_normalized_architecture_for_specific_sdk_version() { + eval $invocation + + local is_version_support_arm64="$(is_arm64_supported "$1")" + local is_channel_support_arm64="$(is_arm64_supported "$2")" + local architecture="$3"; + local osname="$(get_current_os_name)" + + if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then + #check if rosetta is installed + if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then + say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." + echo "x64" + return 0; + else + say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform" + return 1 + fi + fi + + echo "$architecture" + return 0 +} + +# args: +# version or channel - $1 +is_arm64_supported() { + #any channel or version that starts with the specified versions + case "$1" in + ( "1"* | "2"* | "3"* | "4"* | "5"*) + echo false + return 0 + esac + + echo true + return 0 +} + +# args: +# user_defined_os - $1 +get_normalized_os() { + eval $invocation + + local osname="$(to_lowercase "$1")" + if [ ! -z "$osname" ]; then + case "$osname" in + osx | freebsd | rhel.6 | linux-musl | linux) + echo "$osname" + return 0 + ;; + *) + say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." + return 1 + ;; + esac + else + osname="$(get_current_os_name)" || return 1 + fi + echo "$osname" + return 0 +} + +# args: +# quality - $1 +get_normalized_quality() { + eval $invocation + + local quality="$(to_lowercase "$1")" + if [ ! -z "$quality" ]; then + case "$quality" in + daily | signed | validated | preview) + echo "$quality" + return 0 + ;; + ga) + #ga quality is available without specifying quality, so normalizing it to empty + return 0 + ;; + *) + say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, signed, validated, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." + return 1 + ;; + esac + fi + return 0 +} + +# args: +# channel - $1 +get_normalized_channel() { + eval $invocation + + local channel="$(to_lowercase "$1")" + + if [[ $channel == current ]]; then + say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' + fi + + if [[ $channel == release/* ]]; then + say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.'; + fi + + if [ ! -z "$channel" ]; then + case "$channel" in + lts) + echo "LTS" + return 0 + ;; + sts) + echo "STS" + return 0 + ;; + current) + echo "STS" + return 0 + ;; + *) + echo "$channel" + return 0 + ;; + esac + fi + + return 0 +} + +# args: +# runtime - $1 +get_normalized_product() { + eval $invocation + + local product="" + local runtime="$(to_lowercase "$1")" + if [[ "$runtime" == "dotnet" ]]; then + product="dotnet-runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + product="aspnetcore-runtime" + elif [ -z "$runtime" ]; then + product="dotnet-sdk" + fi + echo "$product" + return 0 +} + +# The version text returned from the feeds is a 1-line or 2-line string: +# For the SDK and the dotnet runtime (2 lines): +# Line 1: # commit_hash +# Line 2: # 4-part version +# For the aspnetcore runtime (1 line): +# Line 1: # 4-part version + +# args: +# version_text - stdin +get_version_from_latestversion_file_content() { + eval $invocation + + cat | tail -n 1 | sed 's/\r$//' + return 0 +} + +# args: +# install_root - $1 +# relative_path_to_package - $2 +# specific_version - $3 +is_dotnet_package_installed() { + eval $invocation + + local install_root="$1" + local relative_path_to_package="$2" + local specific_version="${3//[$'\t\r\n']}" + + local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" + say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" + + if [ -d "$dotnet_package_path" ]; then + return 0 + else + return 1 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +get_version_from_latestversion_file() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + + local version_file_url=null + if [[ "$runtime" == "dotnet" ]]; then + version_file_url="$azure_feed/Runtime/$channel/latest.version" + elif [[ "$runtime" == "aspnetcore" ]]; then + version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" + elif [ -z "$runtime" ]; then + version_file_url="$azure_feed/Sdk/$channel/latest.version" + else + say_err "Invalid value for \$runtime" + return 1 + fi + say_verbose "get_version_from_latestversion_file: latest url: $version_file_url" + + download "$version_file_url" || return $? + return 0 +} + +# args: +# json_file - $1 +parse_globaljson_file_for_version() { + eval $invocation + + local json_file="$1" + if [ ! -f "$json_file" ]; then + say_err "Unable to find \`$json_file\`" + return 1 + fi + + sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/') + if [ -z "$sdk_section" ]; then + say_err "Unable to parse the SDK node in \`$json_file\`" + return 1 + fi + + sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') + sdk_list=${sdk_list//[\" ]/} + sdk_list=${sdk_list//,/$'\n'} + + local version_info="" + while read -r line; do + IFS=: + while read -r key value; do + if [[ "$key" == "version" ]]; then + version_info=$value + fi + done <<< "$line" + done <<< "$sdk_list" + if [ -z "$version_info" ]; then + say_err "Unable to find the SDK:version node in \`$json_file\`" + return 1 + fi + + unset IFS; + echo "$version_info" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# version - $4 +# json_file - $5 +get_specific_version_from_version() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local version="$(to_lowercase "$4")" + local json_file="$5" + + if [ -z "$json_file" ]; then + if [[ "$version" == "latest" ]]; then + local version_info + version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_latestversion_file_content + return 0 + else + echo "$version" + return 0 + fi + else + local version_info + version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 + echo "$version_info" + return 0 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +# normalized_os - $5 +construct_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + local specific_product_version="$(get_specific_product_version "$1" "$4")" + local osname="$5" + + local download_link=null + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" + else + return 1 + fi + + echo "$download_link" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +# download link - $3 (optional) +get_specific_product_version() { + # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents + # to resolve the version of what's in the folder, superseding the specified version. + # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link + eval $invocation + + local azure_feed="$1" + local specific_version="${2//[$'\t\r\n']}" + local package_download_link="" + if [ $# -gt 2 ]; then + local package_download_link="$3" + fi + local specific_product_version=null + + # Try to get the version number, using the productVersion.txt file located next to the installer file. + local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link") + $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link")) + + for download_link in "${download_links[@]}" + do + say_verbose "Checking for the existence of $download_link" + + if machine_has "curl" + then + if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then + continue + else + echo "${specific_product_version//[$'\t\r\n']}" + return 0 + fi + + elif machine_has "wget" + then + specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1) + if [ $? = 0 ]; then + echo "${specific_product_version//[$'\t\r\n']}" + return 0 + fi + fi + done + + # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. + say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead." + specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")" + echo "${specific_product_version//[$'\t\r\n']}" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +# is_flattened - $3 +# download link - $4 (optional) +get_specific_product_version_url() { + eval $invocation + + local azure_feed="$1" + local specific_version="$2" + local is_flattened="$3" + local package_download_link="" + if [ $# -gt 3 ]; then + local package_download_link="$4" + fi + + local pvFileName="productVersion.txt" + if [ "$is_flattened" = true ]; then + if [ -z "$runtime" ]; then + pvFileName="sdk-productVersion.txt" + elif [[ "$runtime" == "dotnet" ]]; then + pvFileName="runtime-productVersion.txt" + else + pvFileName="$runtime-productVersion.txt" + fi + fi + + local download_link=null + + if [ -z "$package_download_link" ]; then + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/${pvFileName}" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/${pvFileName}" + else + return 1 + fi + else + download_link="${package_download_link%/*}/${pvFileName}" + fi + + say_verbose "Constructed productVersion link: $download_link" + echo "$download_link" + return 0 +} + +# args: +# download link - $1 +# specific version - $2 +get_product_specific_version_from_download_link() +{ + eval $invocation + + local download_link="$1" + local specific_version="$2" + local specific_product_version="" + + if [ -z "$download_link" ]; then + echo "$specific_version" + return 0 + fi + + #get filename + filename="${download_link##*/}" + + #product specific version follows the product name + #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404 + IFS='-' + read -ra filename_elems <<< "$filename" + count=${#filename_elems[@]} + if [[ "$count" -gt 2 ]]; then + specific_product_version="${filename_elems[2]}" + else + specific_product_version=$specific_version + fi + unset IFS; + echo "$specific_product_version" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_legacy_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + + local distro_specific_osname + distro_specific_osname="$(get_legacy_os_name)" || return 1 + + local legacy_download_link=null + if [[ "$runtime" == "dotnet" ]]; then + legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + elif [ -z "$runtime" ]; then + legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + else + return 1 + fi + + echo "$legacy_download_link" + return 0 +} + +get_user_install_path() { + eval $invocation + + if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then + echo "$DOTNET_INSTALL_DIR" + else + echo "$HOME/.dotnet" + fi + return 0 +} + +# args: +# install_dir - $1 +resolve_installation_path() { + eval $invocation + + local install_dir=$1 + if [ "$install_dir" = "" ]; then + local user_install_path="$(get_user_install_path)" + say_verbose "resolve_installation_path: user_install_path=$user_install_path" + echo "$user_install_path" + return 0 + fi + + echo "$install_dir" + return 0 +} + +# args: +# relative_or_absolute_path - $1 +get_absolute_path() { + eval $invocation + + local relative_or_absolute_path=$1 + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" + return 0 +} + +# args: +# input_files - stdin +# root_path - $1 +# out_path - $2 +# override - $3 +copy_files_or_dirs_from_list() { + eval $invocation + + local root_path="$(remove_trailing_slash "$1")" + local out_path="$(remove_trailing_slash "$2")" + local override="$3" + local osname="$(get_current_os_name)" + local override_switch=$( + if [ "$override" = false ]; then + if [ "$osname" = "linux-musl" ]; then + printf -- "-u"; + else + printf -- "-n"; + fi + fi) + + cat | uniq | while read -r file_path; do + local path="$(remove_beginning_slash "${file_path#$root_path}")" + local target="$out_path/$path" + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then + mkdir -p "$out_path/$(dirname "$path")" + if [ -d "$target" ]; then + rm -rf "$target" + fi + cp -R $override_switch "$root_path/$path" "$target" + fi + done +} + +# args: +# zip_path - $1 +# out_path - $2 +extract_dotnet_package() { + eval $invocation + + local zip_path="$1" + local out_path="$2" + + local temp_out_path="$(mktemp -d "$temporary_file_template")" + + local failed=false + tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true + + local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' + find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false + find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" + + rm -rf "$temp_out_path" + rm -f "$zip_path" && say_verbose "Temporary zip file $zip_path was removed" + + if [ "$failed" = true ]; then + say_err "Extraction failed" + return 1 + fi + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header() +{ + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + + local failed=false + local response + if machine_has "curl"; then + get_http_header_curl $remote_path $disable_feed_credential || failed=true + elif machine_has "wget"; then + get_http_header_wget $remote_path $disable_feed_credential || failed=true + else + failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Failed to get HTTP header: '$remote_path'." + return 1 + fi + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header_curl() { + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + + remote_path_with_credential="$remote_path" + if [ "$disable_feed_credential" = false ]; then + remote_path_with_credential+="$feed_credential" + fi + + curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " + curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header_wget() { + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + local wget_options="-q -S --spider --tries 5 " + + local wget_options_extra='' + + # Test for options that aren't supported on all wget implementations. + if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then + wget_options_extra="--waitretry 2 --connect-timeout 15 " + else + say "wget extra options are unavailable for this environment" + fi + + remote_path_with_credential="$remote_path" + if [ "$disable_feed_credential" = false ]; then + remote_path_with_credential+="$feed_credential" + fi + + wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1 + + return $? +} + +# args: +# remote_path - $1 +# [out_path] - $2 - stdout if not provided +download() { + eval $invocation + + local remote_path="$1" + local out_path="${2:-}" + + if [[ "$remote_path" != "http"* ]]; then + cp "$remote_path" "$out_path" + return $? + fi + + local failed=false + local attempts=0 + while [ $attempts -lt 3 ]; do + attempts=$((attempts+1)) + failed=false + if machine_has "curl"; then + downloadcurl "$remote_path" "$out_path" || failed=true + elif machine_has "wget"; then + downloadwget "$remote_path" "$out_path" || failed=true + else + say_err "Missing dependency: neither curl nor wget was found." + exit 1 + fi + + if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then + break + fi + + say "Download attempt #$attempts has failed: $http_code $download_error_msg" + say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds." + sleep $((attempts*10)) + done + + if [ "$failed" = true ]; then + say_verbose "Download failed: $remote_path" + return 1 + fi + return 0 +} + +# Updates global variables $http_code and $download_error_msg +downloadcurl() { + eval $invocation + unset http_code + unset download_error_msg + local remote_path="$1" + local out_path="${2:-}" + # Append feed_credential as late as possible before calling curl to avoid logging feed_credential + # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output. + local remote_path_with_credential="${remote_path}${feed_credential}" + local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " + local curl_exit_code=0; + if [ -z "$out_path" ]; then + curl $curl_options "$remote_path_with_credential" 2>&1 + curl_exit_code=$? + else + curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1 + curl_exit_code=$? + fi + + if [ $curl_exit_code -gt 0 ]; then + download_error_msg="Unable to download $remote_path." + # Check for curl timeout codes + if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then + download_error_msg+=" Failed to reach the server: connection timeout." + else + local disable_feed_credential=false + local response=$(get_http_header_curl $remote_path $disable_feed_credential) + http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 ) + if [[ ! -z $http_code && $http_code != 2* ]]; then + download_error_msg+=" Returned HTTP status code: $http_code." + fi + fi + say_verbose "$download_error_msg" + return 1 + fi + return 0 +} + + +# Updates global variables $http_code and $download_error_msg +downloadwget() { + eval $invocation + unset http_code + unset download_error_msg + local remote_path="$1" + local out_path="${2:-}" + # Append feed_credential as late as possible before calling wget to avoid logging feed_credential + local remote_path_with_credential="${remote_path}${feed_credential}" + local wget_options="--tries 20 " + + local wget_options_extra='' + local wget_result='' + + # Test for options that aren't supported on all wget implementations. + if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then + wget_options_extra="--waitretry 2 --connect-timeout 15 " + else + say "wget extra options are unavailable for this environment" + fi + + if [ -z "$out_path" ]; then + wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 + wget_result=$? + else + wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 + wget_result=$? + fi + + if [[ $wget_result != 0 ]]; then + local disable_feed_credential=false + local response=$(get_http_header_wget $remote_path $disable_feed_credential) + http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 ) + download_error_msg="Unable to download $remote_path." + if [[ ! -z $http_code && $http_code != 2* ]]; then + download_error_msg+=" Returned HTTP status code: $http_code." + # wget exit code 4 stands for network-issue + elif [[ $wget_result == 4 ]]; then + download_error_msg+=" Failed to reach the server: connection timeout." + fi + say_verbose "$download_error_msg" + return 1 + fi + + return 0 +} + +get_download_link_from_aka_ms() { + eval $invocation + + #quality is not supported for LTS or STS channel + #STS maps to current + if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then + normalized_quality="" + say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." + fi + + say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." + + #construct aka.ms link + aka_ms_link="https://aka.ms/dotnet" + if [ "$internal" = true ]; then + aka_ms_link="$aka_ms_link/internal" + fi + aka_ms_link="$aka_ms_link/$normalized_channel" + if [[ ! -z "$normalized_quality" ]]; then + aka_ms_link="$aka_ms_link/$normalized_quality" + fi + aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz" + say_verbose "Constructed aka.ms link: '$aka_ms_link'." + + #get HTTP response + #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function + #otherwise the redirect link would have credentials as well + #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link + disable_feed_credential=true + response="$(get_http_header $aka_ms_link $disable_feed_credential)" + + say_verbose "Received response: $response" + # Get results of all the redirects. + http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) + # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404). + broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' ) + + # All HTTP codes are 301 (Moved Permanently), the redirect link exists. + if [[ -z "$broken_redirects" ]]; then + aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r') + + if [[ -z "$aka_ms_download_link" ]]; then + say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location." + return 1 + fi + + say_verbose "The redirect location retrieved: '$aka_ms_download_link'." + return 0 + else + say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)." + return 1 + fi +} + +get_feeds_to_use() +{ + feeds=( + "https://dotnetcli.azureedge.net/dotnet" + "https://dotnetbuilds.azureedge.net/public" + ) + + if [[ -n "$azure_feed" ]]; then + feeds=("$azure_feed") + fi + + if [[ "$no_cdn" == "true" ]]; then + feeds=( + "https://dotnetcli.blob.core.windows.net/dotnet" + "https://dotnetbuilds.blob.core.windows.net/public" + ) + + if [[ -n "$uncached_feed" ]]; then + feeds=("$uncached_feed") + fi + fi +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed). +generate_download_links() { + + download_links=() + specific_versions=() + effective_versions=() + link_types=() + + # If generate_akams_links returns false, no fallback to old links. Just terminate. + # This function may also 'exit' (if the determined version is already installed). + generate_akams_links || return + + # Check other feeds only if we haven't been able to find an aka.ms link. + if [[ "${#download_links[@]}" -lt 1 ]]; then + for feed in ${feeds[@]} + do + # generate_regular_links may also 'exit' (if the determined version is already installed). + generate_regular_links $feed || return + done + fi + + if [[ "${#download_links[@]}" -eq 0 ]]; then + say_err "Failed to resolve the exact version number." + return 1 + fi + + say_verbose "Generated ${#download_links[@]} links." + for link_index in ${!download_links[@]} + do + say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}" + done +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed). +generate_akams_links() { + local valid_aka_ms_link=true; + + normalized_version="$(to_lowercase "$version")" + if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then + say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." + return 1 + fi + + if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then + # aka.ms links are not needed when exact version is specified via command or json file + return + fi + + get_download_link_from_aka_ms || valid_aka_ms_link=false + + if [[ "$valid_aka_ms_link" == true ]]; then + say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." + say_verbose "Downloading using legacy url will not be attempted." + + download_link=$aka_ms_download_link + + #get version from the path + IFS='/' + read -ra pathElems <<< "$download_link" + count=${#pathElems[@]} + specific_version="${pathElems[count-2]}" + unset IFS; + say_verbose "Version: '$specific_version'." + + #Retrieve effective version + effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" + + # Add link info to arrays + download_links+=($download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("aka.ms") + + # Check if the SDK version is already installed. + if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "$asset_name with version '$effective_version' is already installed." + exit 0 + fi + + return 0 + fi + + # if quality is specified - exit with error - there is no fallback approach + if [ ! -z "$normalized_quality" ]; then + say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." + return 1 + fi + say_verbose "Falling back to latest.version file approach." +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed) +# args: +# feed - $1 +generate_regular_links() { + local feed="$1" + local valid_legacy_download_link=true + + specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0' + + if [[ "$specific_version" == '0' ]]; then + say_verbose "Failed to resolve the specific version number using feed '$feed'" + return + fi + + effective_version="$(get_specific_product_version "$feed" "$specific_version")" + say_verbose "specific_version=$specific_version" + + download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" + say_verbose "Constructed primary named payload URL: $download_link" + + # Add link info to arrays + download_links+=($download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("primary") + + legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false + + if [ "$valid_legacy_download_link" = true ]; then + say_verbose "Constructed legacy named payload URL: $legacy_download_link" + + download_links+=($legacy_download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("legacy") + else + legacy_download_link="" + say_verbose "Cound not construct a legacy_download_link; omitting..." + fi + + # Check if the SDK version is already installed. + if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "$asset_name with version '$effective_version' is already installed." + exit 0 + fi +} + +print_dry_run() { + + say "Payload URLs:" + + for link_index in "${!download_links[@]}" + do + say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}" + done + + resolved_version=${specific_versions[0]} + repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\""" + + if [ ! -z "$normalized_quality" ]; then + repeatable_command+=" --quality "\""$normalized_quality"\""" + fi + + if [[ "$runtime" == "dotnet" ]]; then + repeatable_command+=" --runtime "\""dotnet"\""" + elif [[ "$runtime" == "aspnetcore" ]]; then + repeatable_command+=" --runtime "\""aspnetcore"\""" + fi + + repeatable_command+="$non_dynamic_parameters" + + if [ -n "$feed_credential" ]; then + repeatable_command+=" --feed-credential "\"""\""" + fi + + say "Repeatable invocation: $repeatable_command" +} + +calculate_vars() { + eval $invocation + + script_name=$(basename "$0") + normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" + say_verbose "Normalized architecture: '$normalized_architecture'." + normalized_os="$(get_normalized_os "$user_defined_os")" + say_verbose "Normalized OS: '$normalized_os'." + normalized_quality="$(get_normalized_quality "$quality")" + say_verbose "Normalized quality: '$normalized_quality'." + normalized_channel="$(get_normalized_channel "$channel")" + say_verbose "Normalized channel: '$normalized_channel'." + normalized_product="$(get_normalized_product "$runtime")" + say_verbose "Normalized product: '$normalized_product'." + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: '$install_root'." + + normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")" + + if [[ "$runtime" == "dotnet" ]]; then + asset_relative_path="shared/Microsoft.NETCore.App" + asset_name=".NET Core Runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + asset_relative_path="shared/Microsoft.AspNetCore.App" + asset_name="ASP.NET Core Runtime" + elif [ -z "$runtime" ]; then + asset_relative_path="sdk" + asset_name=".NET Core SDK" + fi + + get_feeds_to_use +} + +install_dotnet() { + eval $invocation + local download_failed=false + local download_completed=false + + mkdir -p "$install_root" + zip_path="$(mktemp "$temporary_file_template")" + say_verbose "Zip path: $zip_path" + + for link_index in "${!download_links[@]}" + do + download_link="${download_links[$link_index]}" + specific_version="${specific_versions[$link_index]}" + effective_version="${effective_versions[$link_index]}" + link_type="${link_types[$link_index]}" + + say "Attempting to download using $link_type link $download_link" + + # The download function will set variables $http_code and $download_error_msg in case of failure. + download_failed=false + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + if [ "$download_failed" = true ]; then + case $http_code in + 404) + say "The resource at $link_type link '$download_link' is not available." + ;; + *) + say "Failed to download $link_type link '$download_link': $download_error_msg" + ;; + esac + rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed" + else + download_completed=true + break + fi + done + + if [[ "$download_completed" == false ]]; then + say_err "Could not find \`$asset_name\` with version = $specific_version" + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" + return 1 + fi + + say "Extracting zip from $download_link" + extract_dotnet_package "$zip_path" "$install_root" || return 1 + + # Check if the SDK version is installed; if not, fail the installation. + # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. + if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then + IFS='-' + read -ra verArr <<< "$specific_version" + release_version="${verArr[0]}" + unset IFS; + say_verbose "Checking installation: version = $release_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then + say "Installed version is $effective_version" + return 0 + fi + fi + + # Check if the standard SDK version is installed. + say_verbose "Checking installation: version = $effective_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "Installed version is $effective_version" + return 0 + fi + + # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. + say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues." + say_err "\`$asset_name\` with version = $effective_version failed to install with an error." + return 1 +} + +args=("$@") + +local_version_file_relative_path="/.version" +bin_folder_relative_path="" +temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" + +channel="LTS" +version="Latest" +json_file="" +install_dir="" +architecture="" +dry_run=false +no_path=false +no_cdn=false +azure_feed="" +uncached_feed="" +feed_credential="" +verbose=false +runtime="" +runtime_id="" +quality="" +internal=false +override_non_versioned_files=true +non_dynamic_parameters="" +user_defined_os="" + +while [ $# -ne 0 ] +do + name="$1" + case "$name" in + -c|--channel|-[Cc]hannel) + shift + channel="$1" + ;; + -v|--version|-[Vv]ersion) + shift + version="$1" + ;; + -q|--quality|-[Qq]uality) + shift + quality="$1" + ;; + --internal|-[Ii]nternal) + internal=true + non_dynamic_parameters+=" $name" + ;; + -i|--install-dir|-[Ii]nstall[Dd]ir) + shift + install_dir="$1" + ;; + --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) + shift + architecture="$1" + ;; + --os|-[Oo][SS]) + shift + user_defined_os="$1" + ;; + --shared-runtime|-[Ss]hared[Rr]untime) + say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." + if [ -z "$runtime" ]; then + runtime="dotnet" + fi + ;; + --runtime|-[Rr]untime) + shift + runtime="$1" + if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then + say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." + if [[ "$runtime" == "windowsdesktop" ]]; then + say_err "WindowsDesktop archives are manufactured for Windows platforms only." + fi + exit 1 + fi + ;; + --dry-run|-[Dd]ry[Rr]un) + dry_run=true + ;; + --no-path|-[Nn]o[Pp]ath) + no_path=true + non_dynamic_parameters+=" $name" + ;; + --verbose|-[Vv]erbose) + verbose=true + non_dynamic_parameters+=" $name" + ;; + --no-cdn|-[Nn]o[Cc]dn) + no_cdn=true + non_dynamic_parameters+=" $name" + ;; + --azure-feed|-[Aa]zure[Ff]eed) + shift + azure_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --uncached-feed|-[Uu]ncached[Ff]eed) + shift + uncached_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --feed-credential|-[Ff]eed[Cc]redential) + shift + feed_credential="$1" + #feed_credential should start with "?", for it to be added to the end of the link. + #adding "?" at the beginning of the feed_credential if needed. + [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential" + ;; + --runtime-id|-[Rr]untime[Ii]d) + shift + runtime_id="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead." + ;; + --jsonfile|-[Jj][Ss]on[Ff]ile) + shift + json_file="$1" + ;; + --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) + override_non_versioned_files=false + non_dynamic_parameters+=" $name" + ;; + -?|--?|-h|--help|-[Hh]elp) + script_name="$(basename "$0")" + echo ".NET Tools Installer" + echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]" + echo " $script_name -h|-?|--help" + echo "" + echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" + echo " - The SDK needs to be installed without user interaction and without admin rights." + echo " - The SDK installation doesn't need to persist across multiple CI runs." + echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." + echo "" + echo "Options:" + echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." + echo " -Channel" + echo " Possible values:" + echo " - STS - the most recent Standard Term Support release" + echo " - LTS - the most recent Long Term Support release" + echo " - 2-part version in a format A.B - represents a specific release" + echo " examples: 2.0; 1.0" + echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release" + echo " examples: 5.0.1xx, 5.0.2xx." + echo " Supported since 5.0 release" + echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead." + echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used." + echo " -v,--version Use specific VERSION, Defaults to \`$version\`." + echo " -Version" + echo " Possible values:" + echo " - latest - the latest build on specific channel" + echo " - 3-part version in a format A.B.C - represents specific version of build" + echo " examples: 2.0.0-preview2-006120; 1.1.0" + echo " -q,--quality Download the latest build of specified quality in the channel." + echo " -Quality" + echo " The possible values are: daily, signed, validated, preview, GA." + echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." + echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported." + echo " Supported since 5.0 release." + echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality." + echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter." + echo " --feed-credential Token to access Azure feed. Used as a query string to append to the Azure feed." + echo " -FeedCredential This parameter typically is not specified." + echo " -i,--install-dir Install under specified location (see Install Location below)" + echo " -InstallDir" + echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." + echo " --arch,-Architecture,-Arch" + echo " Possible values: x64, arm, arm64, s390x and ppc64le" + echo " --os Specifies operating system to be used when selecting the installer." + echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." + echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." + echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links." + echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information." + echo " --runtime Installs a shared runtime only, without the SDK." + echo " -Runtime" + echo " Possible values:" + echo " - dotnet - the Microsoft.NETCore.App shared runtime" + echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" + echo " --dry-run,-DryRun Do not perform installation. Display download link." + echo " --no-path, -NoPath Do not set PATH for the current process." + echo " --verbose,-Verbose Display diagnostics information." + echo " --azure-feed,-AzureFeed For internal use only." + echo " Allows using a different storage to download SDK archives from." + echo " This parameter is only used if --no-cdn is false." + echo " --uncached-feed,-UncachedFeed For internal use only." + echo " Allows using a different storage to download SDK archives from." + echo " This parameter is only used if --no-cdn is true." + echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." + echo " -SkipNonVersionedFiles" + echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly." + echo " --jsonfile Determines the SDK version from a user specified global.json file." + echo " Note: global.json must have a value for 'SDK:Version'" + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Install Location:" + echo " Location is chosen in following order:" + echo " - --install-dir option" + echo " - Environmental variable DOTNET_INSTALL_DIR" + echo " - $HOME/.dotnet" + exit 0 + ;; + *) + say_err "Unknown argument \`$name\`" + exit 1 + ;; + esac + + shift +done + +say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +say_verbose "- The SDK needs to be installed without user interaction and without admin rights." +say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." +say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" + +if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then + message="Provide credentials via --feed-credential parameter." + if [ "$dry_run" = true ]; then + say_warning "$message" + else + say_err "$message" + exit 1 + fi +fi + +check_min_reqs +calculate_vars +# generate_regular_links call below will 'exit' if the determined version is already installed. +generate_download_links + +if [[ "$dry_run" = true ]]; then + print_dry_run + exit 0 +fi + +install_dotnet + +bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" +if [ "$no_path" = false ]; then + say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." + export PATH="$bin_path":"$PATH" +else + say "Binaries of dotnet can be found in $bin_path" +fi + +say "Note that the script does not resolve dependencies during installation." +say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." +say "Installation finished successfully." diff --git a/test/dotnet/dotnet_env.sh b/test/dotnet/dotnet_env.sh new file mode 100644 index 000000000..37b14ea2b --- /dev/null +++ b/test/dotnet/dotnet_env.sh @@ -0,0 +1,4 @@ +#!/bin/bash +export DOTNET_NOLOGO=true +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true +export DOTNET_GENERATE_ASPNET_CERTIFICATE=false \ No newline at end of file diff --git a/test/dotnet/dotnet_helpers.sh b/test/dotnet/dotnet_helpers.sh new file mode 100644 index 000000000..6c833b444 --- /dev/null +++ b/test/dotnet/dotnet_helpers.sh @@ -0,0 +1,71 @@ +#!/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://dotnetcli.azureedge.net/dotnet/Runtime/$channel/latest.version" + elif [ "$runtime" = "aspnetcore" ]; then + wget -qO- "https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$channel/latest.version" + else + wget -qO- "https://dotnetcli.azureedge.net/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 +} + +# Asserts that the specified .NET SDK version is installed +# Returns a non-zero exit code if the check fails +# Usage: is_dotnet_sdk_version_installed +# Example: is_dotnet_sdk_version_installed "6.0" +# Example: is_dotnet_sdk_version_installed "6.0.412" +is_dotnet_sdk_version_installed() { + local expected="$1" + dotnet --list-sdks | grep --fixed-strings --silent "$expected" + return $? +} + + +# Asserts that the specified .NET Runtime version is installed +# Returns a non-zero exit code if the check fails +# Usage: is_dotnet_runtime_version_installed +# Example: is_dotnet_runtime_version_installed "6.0" +# Example: is_dotnet_runtime_version_installed "6.0.412" +is_dotnet_runtime_version_installed() { + local expected="$1" + dotnet --list-runtimes | grep --fixed-strings --silent "Microsoft.NETCore.App $expected" + return $? +} + +# Asserts that the specified ASP.NET Core Runtime version is installed +# Returns a non-zero exit code if the check fails +# Usage: is_aspnetcore_runtime_version_installed +# Example: is_aspnetcore_runtime_version_installed "6.0" +# Example: is_aspnetcore_runtime_version_installed "6.0.412" +is_aspnetcore_runtime_version_installed() { + local expected="$1" + dotnet --list-runtimes | grep --fixed-strings --silent "Microsoft.AspNetCore.App $expected" + return $? +} \ No newline at end of file diff --git a/test/dotnet/install_additional_dotnet.sh b/test/dotnet/install_additional_dotnet.sh deleted file mode 100644 index e69c7dce1..000000000 --- a/test/dotnet/install_additional_dotnet.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e - -# Optional: Import test library -source dev-container-features-test-lib - -check "dotnet version 6.0.301 installed as default" bash -c "dotnet --version | grep 6.0.301" -check "dotnet version 5.0 installed" bash -c "ls -l /usr/local/dotnet | grep 5.0" -check "dotnet version 3.1.420 installed" bash -c "ls -l /usr/local/dotnet | grep 3.1.420" - -# Verify current symlink exists and works -check "current link dotnet" /usr/local/dotnet/current/dotnet --info -check "current link sdk" ls -l /usr/local/dotnet/current/sdk - -# Report result -reportResults diff --git a/test/dotnet/install_aspnetcore_runtime_only.sh b/test/dotnet/install_aspnetcore_runtime_only.sh new file mode 100644 index 000000000..35bd540b7 --- /dev/null +++ b/test/dotnet/install_aspnetcore_runtime_only.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md#dev-container-features-test-lib +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. Syntax is... +# check