diff --git a/.github/actions/smoke-test/build.sh b/.github/actions/smoke-test/build.sh index cf949babfa..4b985ce60a 100755 --- a/.github/actions/smoke-test/build.sh +++ b/.github/actions/smoke-test/build.sh @@ -9,9 +9,9 @@ echo "(*) Pulling latest '@devcontainer/cli" #Temporarily installing cli from source until https://github.com/devcontainers/cli/pull/6 is merged cd build -chmod +x devcontainers-cli-0.6.3.tgz +chmod +x devcontainers-cli-0.6.4.tgz echo "(*) Building image - ${IMAGE}" -npx --yes devcontainers-cli-0.6.3.tgz up --workspace-folder ../src/${IMAGE} +npx --yes devcontainers-cli-0.6.4.tgz up --workspace-folder ../src/${IMAGE} # devcontainer build --workspace-folder "src/${IMAGE}/" --image-name vsc-${IMAGE} diff --git a/.github/actions/smoke-test/test.sh b/.github/actions/smoke-test/test.sh index 2afc5b8f9f..f508a537b9 100755 --- a/.github/actions/smoke-test/test.sh +++ b/.github/actions/smoke-test/test.sh @@ -7,9 +7,9 @@ set -e # Run actual test echo "(*) Running test..." cd build -chmod +x devcontainers-cli-0.6.3.tgz +chmod +x devcontainers-cli-0.6.4.tgz -npx --yes devcontainers-cli-0.6.3.tgz exec --workspace-folder $(pwd)/../src/$IMAGE /bin/sh -c 'set -e && if [ -f "test-project/test.sh" ]; then cd test-project && if [ "$(id -u)" = "0" ]; then chmod +x test.sh; else sudo chmod +x test.sh; fi && ./test.sh; else ls -a; fi' +npx --yes devcontainers-cli-0.6.4.tgz exec --workspace-folder $(pwd)/../src/$IMAGE /bin/sh -c 'set -e && if [ -f "test-project/test.sh" ]; then cd test-project && if [ "$(id -u)" = "0" ]; then chmod +x test.sh; else sudo chmod +x test.sh; fi && ./test.sh; else ls -a; fi' # Clean up docker stop $(docker container ls -q) diff --git a/build/devcontainers-cli-0.6.4.tgz b/build/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/build/devcontainers-cli-0.6.4.tgz differ diff --git a/build/src/push.js b/build/src/push.js index 211ef8e9f0..77218e1385 100644 --- a/build/src/push.js +++ b/build/src/push.js @@ -139,7 +139,7 @@ async function pushImage(definitionId, repo, release, updateLatest, } const spawnOpts = { stdio: 'inherit', cwd: workingDir, shell: true }; - await asyncUtils.spawn('npx --yes devcontainers-cli-0.6.3.tgz', [ + await asyncUtils.spawn('npx --yes devcontainers-cli-0.6.4.tgz', [ 'build', '--workspace-folder', definitionPath, '--log-level ', 'info', diff --git a/src/alpine/devcontainers-cli-0.6.4.tgz b/src/alpine/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/alpine/devcontainers-cli-0.6.4.tgz differ diff --git a/src/anaconda/devcontainers-cli-0.6.4.tgz b/src/anaconda/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/anaconda/devcontainers-cli-0.6.4.tgz differ diff --git a/src/anaconda/test-project/test-utils.sh b/src/anaconda/test-project/test-utils.sh new file mode 100644 index 0000000000..6994afdf70 --- /dev/null +++ b/src/anaconda/test-project/test-utils.sh @@ -0,0 +1,148 @@ +#!/bin/bash +SCRIPT_FOLDER="$(cd "$(dirname $0)" && pwd)" +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +echoStderr() +{ + echo "$@" 1>&2 +} + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echoStderr -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echoStderr -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} + +fixTestProjectFolderPrivs() { + if [ "${USERNAME}" != "root" ]; then + TEST_PROJECT_FOLDER="${1:-$SCRIPT_FOLDER}" + FOLDER_USER="$(stat -c '%U' "${TEST_PROJECT_FOLDER}")" + if [ "${FOLDER_USER}" != "${USERNAME}" ]; then + echoStderr "WARNING: Test project folder is owned by ${FOLDER_USER}. Updating to ${USERNAME}." + sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" + fi + fi +} diff --git a/src/anaconda/test-project/test.sh b/src/anaconda/test-project/test.sh new file mode 100755 index 0000000000..960efcdf3e --- /dev/null +++ b/src/anaconda/test-project/test.sh @@ -0,0 +1,20 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Image specific tests +check "conda" conda --version +check "python" python --version +check "pylint" pylint --version +check "flake8" flake8 --version +check "autopep8" autopep8 --version +check "yapf" yapf --version +check "pydocstyle" pydocstyle --version +check "pycodestyle" pycodestyle --version + +# Report result +reportResults diff --git a/src/codespaces/.devcontainer.json b/src/codespaces/.devcontainer.json index cec363952c..64539bc1ee 100644 --- a/src/codespaces/.devcontainer.json +++ b/src/codespaces/.devcontainer.json @@ -35,187 +35,100 @@ ] } }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "username": "codespace", - "user_uid": "1000", - "user_gid": "1000" - } - }, - { - "id": "devcontainers/features/dotnet@v0.0.2", - "options": { - "version": "3", - "install_using_apt": "false", - "override_default_version": "false" - } - }, - { - "id": "devcontainers/features/dotnet@v0.0.2", - "options": { - "version": "6", - "install_using_apt": "false", - "override_default_version": "true" - } - }, - { - "id": "devcontainers/features/hugo@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "16" - } - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "14" - } - }, - { - "id": "./local-features/nvs", - "options": {} - }, - { - "id": "devcontainers/features/python@v0.0.2", - "options": { - "version": "3.9", - "override_default_version": "false", - "install_jupyterlab": "true" - } - }, - { - "id": "devcontainers/features/python@v0.0.2", - "options": { - "version": "3.10", - "override_default_version": "true", - "install_jupyterlab": "true" - } - }, - { - "id": "./local-features/machine-learning-packages", - "options": {} - }, - { - "id": "devcontainers/features/php@v0.0.2", - "options": { - "version": "8.0.16", - "install_composer": "true", - "override_default_version": "false" - } - }, - { - "id": "devcontainers/features/php@v0.0.2", - "options": { - "version": "8.1.4", - "install_composer": "true", - "override_default_version": "true" - } - }, - { - "id": "devcontainers/features/anaconda@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/ruby@v0.0.2", - "options": { - "version": "3.0" - } - }, - { - "id": "devcontainers/features/ruby@v0.0.2", - "options": { - "version": "3.1" - } - }, - { - "id": "devcontainers/features/java@v0.0.2", - "options": { - "version": "17" - } - }, - { - "id": "devcontainers/features/java@v0.0.2", - "options": { - "version": "11" - } - }, - { - "id": "./local-features/maven", - "options": { - "version": "3" - } - }, - { - "id": "devcontainers/features/sshd@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/git-lfs@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/github-cli@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/docker-in-docker@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/kubectl-helm-minikube@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/git@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "./local-features/gradle", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/go@v0.0.2", - "options": { - "version": "latest" - } - }, - { - "id": "./local-features/jekyll", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/oryx@v0.0.2", - "options": {} - }, - { - "id": "./local-features/setup-user", - "options": {} + "features": { + "devcontainers/features/common@v0.0.2": { + "username": "codespace", + "user_uid": "1000", + "user_gid": "1000" + }, + "devcontainers/features/dotnet@v0.0.2": { + "version": "6", + "install_using_apt": "false", + "additional_versions": "3" + }, + "devcontainers/features/hugo@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/node@v0.0.2": { + "version": "16", + "additional_versions": "14" + }, + "./local-features/nvs": "latest", + "devcontainers/features/python@v0.0.2": { + "version": "3.10", + "additional_versions": "3.9", + "install_jupyterlab": "true" + }, + "./local-features/machine-learning-packages": "latest", + "devcontainers/features/php@v0.0.2": { + "version": "8.1.4", + "additional_versions": "8.0.16", + "install_composer": "true" + }, + "devcontainers/features/anaconda@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/ruby@v0.0.2": { + "version": "3.1", + "additional_versions": "3.0" + }, + "devcontainers/features/java@v0.0.2": { + "version": "17", + "additional_versions": "11" + }, + "./local-features/maven": { + "version": "3" + }, + "devcontainers/features/sshd@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/git-lfs@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/github-cli@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/docker-in-docker@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/kubectl-helm-minikube@v0.0.2": { + "version": "latest" + }, + "devcontainers/features/git@v0.0.2": { + "version": "latest" + }, + "./local-features/gradle": "latest", + "devcontainers/features/go@v0.0.2": { + "version": "latest" } + "./local-features/jekyll": "latest", + "devcontainers/features/oryx@v0.0.2": "latest", + "./local-features/setup-user": "latest" + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2", + "devcontainers/features/dotnet@v0.0.2", + "devcontainers/features/hugo@v0.0.2", + "devcontainers/features/node@v0.0.2", + "./local-features/nvs", + "devcontainers/features/python@v0.0.2", + "./local-features/machine-learning-packages", + "devcontainers/features/php@v0.0.2", + "devcontainers/features/anaconda@v0.0.2", + "devcontainers/features/ruby@v0.0.2", + "devcontainers/features/java@v0.0.2", + "./local-features/maven", + "devcontainers/features/sshd@v0.0.2", + "devcontainers/features/git-lfs@v0.0.2", + "devcontainers/features/github-cli@v0.0.2", + "devcontainers/features/docker-in-docker@v0.0.2", + "devcontainers/features/kubectl-helm-minikube@v0.0.2", + "devcontainers/features/git@v0.0.2", + "./local-features/gradle", + "devcontainers/features/go@v0.0.2", + "./local-features/jekyll", + "devcontainers/features/oryx@v0.0.2", + "./local-features/setup-user" ], "remoteUser": "codespace", "overrideCommand": false, @@ -233,4 +146,4 @@ // "forwardPorts": [], // "oryx build" will automatically install your dependencies and attempt to build your project "postCreateCommand": "oryx build -p virtualenv_name=.venv --log-file /tmp/oryx-build.log --manifest-dir /tmp || echo 'Could not auto-build. Skipping.'" -} \ No newline at end of file +} diff --git a/src/codespaces/test-project/test-utils.sh b/src/codespaces/test-project/test-utils.sh index 6ce8ef4cb6..093aa7d43f 100644 --- a/src/codespaces/test-project/test-utils.sh +++ b/src/codespaces/test-project/test-utils.sh @@ -145,4 +145,20 @@ fixTestProjectFolderPrivs() { sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" fi fi -} \ No newline at end of file +} + +checkVersionCount() { + LABEL=$1 + count=$2 + expectedCount=$3 + echo -e "\n🧪 Testing $LABEL" + + if [ "$count" == "$expectedCount" ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} diff --git a/src/codespaces/test-project/test.sh b/src/codespaces/test-project/test.sh index ca84c87dd8..fc4f9e0ee3 100755 --- a/src/codespaces/test-project/test.sh +++ b/src/codespaces/test-project/test.sh @@ -11,9 +11,10 @@ check "oryx" oryx --version # Check .NET check "dotnet" dotnet --list-sdks -check "oryx-install-dotnet-2.1" oryx prep --skip-detection --platforms-and-versions dotnet=2.1.12 -check "dotnet-2-installed-by-oryx" ls /usr/local/oryx-platforms/dotnet/ | grep 2.1 -echo $(echo "dotnet versions" && ls -a /usr/local/dotnet) +count=$(ls /usr/local/dotnet | wc -l) +expectedCount=3 # 2 version folders + 1 current folder which links to either one of the version +checkVersionCount "two versions of dotnet are present" $count $expectedCount +echo $(echo "list of installed dotnet versions" && ls -a /usr/local/dotnet) # Check Python check "python" python --version @@ -29,6 +30,9 @@ check "mypy" mypy --version check "pydocstyle" pydocstyle --version check "bandit" bandit --version check "virtualenv" virtualenv --version +count=$(ls /usr/local/python | wc -l) +expectedCount=3 # 2 version folders + 1 current folder which links to either one of the version +checkVersionCount "two versions of python are present" $count $expectedCount echo $(echo "python versions" && ls -a /usr/local/python) echo $(echo "pip list" pip list) @@ -52,6 +56,9 @@ check "java" java -version check "sdkman" bash -c ". /usr/local/sdkman/bin/sdkman-init.sh && sdk version" check "gradle" gradle --version check "maven" mvn --version +count=$(ls /usr/local/sdkman/candidates/java | wc -l) +expectedCount=3 # 2 version folders + 1 current folder which links to either one of the version +checkVersionCount "two versions of java are present" $count $expectedCount echo $(echo "java versions" && ls -a /usr/local/sdkman/candidates/java) # Check Ruby tools @@ -61,6 +68,9 @@ check "rbenv" bash -c 'eval "$(rbenv init -)" && rbenv --version' check "gems" gem --version check "rake" rake --version check "jekyll" jekyll --version +count=$(ls /usr/local/rvm/gems | wc -l) +expectedCount=6 # 2 version folders + 2 global folders for each version + 1 default folder which links to either one of the version + 1 cache folder +checkVersionCount "two versions of ruby are present" $count $expectedCount echo $(echo "ruby versions" && ls -a /usr/local/rvm/rubies) # Node.js @@ -69,6 +79,9 @@ check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm --version" check "nvs" bash -c ". /usr/local/nvs/nvs.sh && nvs --version" check "yarn" yarn --version check "npm" npm --version +count=$(ls /usr/local/share/nvm/versions/node | wc -l) +expectedCount=2 +checkVersionCount "two versions of node are present" $count $expectedCount echo $(echo "node versions" && ls -a /usr/local/share/nvm/versions/node) # PHP @@ -76,6 +89,9 @@ check "php" php --version check "php composer" composer --version check "pecl" pecl version check "Xdebug" php --version | grep 'Xdebug' +count=$(ls /usr/local/php | wc -l) +expectedCount=3 # 2 version folders + 1 current folder which links to either one of the version +checkVersionCount "two versions of php are present" $count $expectedCount echo $(echo "php versions" && ls -a /usr/local/php) # Hugo diff --git a/src/cpp/devcontainers-cli-0.6.4.tgz b/src/cpp/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/cpp/devcontainers-cli-0.6.4.tgz differ diff --git a/src/debian/devcontainers-cli-0.6.4.tgz b/src/debian/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/debian/devcontainers-cli-0.6.4.tgz differ diff --git a/src/dotnet/.devcontainer.json b/src/dotnet/.devcontainer.json index 5378e8c086..d99993c076 100644 --- a/src/dotnet/.devcontainer.json +++ b/src/dotnet/.devcontainer.json @@ -1,26 +1,23 @@ { - "build": { - "dockerfile": "./Dockerfile", - "context": "." - }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "none" - } - } - ], + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "features": { + "devcontainers/features/common@v0.0.2": { + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" + }, + "devcontainers/features/node@v0.0.2": { + "version": "none" + } + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2" + ], // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. diff --git a/src/dotnet/devcontainers-cli-0.6.4.tgz b/src/dotnet/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/dotnet/devcontainers-cli-0.6.4.tgz differ diff --git a/src/dotnet/test-project/test.sh b/src/dotnet/test-project/test.sh index eb86fd5efa..a1228ba9c0 100644 --- a/src/dotnet/test-project/test.sh +++ b/src/dotnet/test-project/test.sh @@ -6,7 +6,7 @@ source test-utils.sh vscode # Run common tests checkCommon -# Definition specific tests +# Image specific tests check "dotnet" dotnet --info check "nuget" dotnet restore check "msbuild" dotnet msbuild @@ -16,4 +16,4 @@ check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" check "yarn" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version" # Report result -reportResults \ No newline at end of file +reportResults diff --git a/src/go/.devcontainer.json b/src/go/.devcontainer.json index 945a480659..afd354748e 100644 --- a/src/go/.devcontainer.json +++ b/src/go/.devcontainer.json @@ -3,29 +3,23 @@ "dockerfile": "./Dockerfile", "context": "." }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } + "features": { + "devcontainers/features/common@v0.0.2": { + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" }, - { - "id": "devcontainers/features/go@v0.0.2", - "options": { - "version": "none" - } + "devcontainers/features/go@v0.0.2": { + "version": "none" }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "none" - } + "devcontainers/features/node@v0.0.2": { + "version": "none" } + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2" ], "runArgs": [ "--cap-add=SYS_PTRACE", @@ -56,4 +50,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/go/devcontainers-cli-0.6.4.tgz b/src/go/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/go/devcontainers-cli-0.6.4.tgz differ diff --git a/src/go/test-project/test-utils.sh b/src/go/test-project/test-utils.sh new file mode 100644 index 0000000000..6994afdf70 --- /dev/null +++ b/src/go/test-project/test-utils.sh @@ -0,0 +1,148 @@ +#!/bin/bash +SCRIPT_FOLDER="$(cd "$(dirname $0)" && pwd)" +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +echoStderr() +{ + echo "$@" 1>&2 +} + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echoStderr -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echoStderr -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} + +fixTestProjectFolderPrivs() { + if [ "${USERNAME}" != "root" ]; then + TEST_PROJECT_FOLDER="${1:-$SCRIPT_FOLDER}" + FOLDER_USER="$(stat -c '%U' "${TEST_PROJECT_FOLDER}")" + if [ "${FOLDER_USER}" != "${USERNAME}" ]; then + echoStderr "WARNING: Test project folder is owned by ${FOLDER_USER}. Updating to ${USERNAME}." + sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" + fi + fi +} diff --git a/src/go/test-project/test.sh b/src/go/test-project/test.sh new file mode 100755 index 0000000000..c3ab5c2ce3 --- /dev/null +++ b/src/go/test-project/test.sh @@ -0,0 +1,16 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Image specific tests +check "go" go version +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" +check "yarn" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version" + +# Report result +reportResults diff --git a/src/java/.devcontainer.json b/src/java/.devcontainer.json index a7c3b61cb3..b6f01c8a41 100644 --- a/src/java/.devcontainer.json +++ b/src/java/.devcontainer.json @@ -3,31 +3,14 @@ "dockerfile": "./Dockerfile", "context": "." }, - "features": [ - { - "id": "devcontainers/features/java@v0.0.2", - "options": { - "version": "none" - } - }, - { - "id": "./local-features/maven", - "options": { - "version": "latest" - } - }, - { - "id": "./local-features/gradle", - "options": { - "version": "latest" - } - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "none" - } - } + "features": { + "devcontainers/features/java@v0.0.2": "none", + "./local-features/maven": "latest", + "./local-features/gradle": "latest", + "devcontainers/features/node@v0.0.2": "none" + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/java@v0.0.2" ], // Configure tool-specific properties. "customizations": { diff --git a/src/java/devcontainers-cli-0.6.4.tgz b/src/java/devcontainers-cli-0.6.4.tgz new file mode 100644 index 0000000000..c67f622359 Binary files /dev/null and b/src/java/devcontainers-cli-0.6.4.tgz differ diff --git a/src/java/test-project/test.sh b/src/java/test-project/test.sh old mode 100644 new mode 100755 index 84f28f0622..ba03fb4f6f --- a/src/java/test-project/test.sh +++ b/src/java/test-project/test.sh @@ -13,10 +13,15 @@ mv maven-wrapper-maven-wrapper-0.5.5/mvnw mvnw mv maven-wrapper-maven-wrapper-0.5.5/.mvn .mvn rm -rf mv maven-wrapper-maven-wrapper-0.5.5 -# Definition specific tests +# Image specific tests check "java" java -version +check "gradle" gradle --version +check "maven" mvn --version check "build-and-test-jar" ./mvnw -q package check "test-project" java -jar target/my-app-1.0-SNAPSHOT.jar +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" +check "yarn" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version" # Clean up rm -f mvnw diff --git a/src/jekyll/devcontainers-cli-0.6.4.tgz b/src/jekyll/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/jekyll/devcontainers-cli-0.6.4.tgz differ diff --git a/src/jekyll/test-project/test-utils.sh b/src/jekyll/test-project/test-utils.sh new file mode 100644 index 0000000000..6994afdf70 --- /dev/null +++ b/src/jekyll/test-project/test-utils.sh @@ -0,0 +1,148 @@ +#!/bin/bash +SCRIPT_FOLDER="$(cd "$(dirname $0)" && pwd)" +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +echoStderr() +{ + echo "$@" 1>&2 +} + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echoStderr -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echoStderr -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} + +fixTestProjectFolderPrivs() { + if [ "${USERNAME}" != "root" ]; then + TEST_PROJECT_FOLDER="${1:-$SCRIPT_FOLDER}" + FOLDER_USER="$(stat -c '%U' "${TEST_PROJECT_FOLDER}")" + if [ "${FOLDER_USER}" != "${USERNAME}" ]; then + echoStderr "WARNING: Test project folder is owned by ${FOLDER_USER}. Updating to ${USERNAME}." + sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" + fi + fi +} diff --git a/src/jekyll/test-project/test.sh b/src/jekyll/test-project/test.sh new file mode 100755 index 0000000000..7f6eb8592f --- /dev/null +++ b/src/jekyll/test-project/test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Image specific tests +check "jekyll" jekyll --version +check "gem" gem --version +check "ruby" ruby --version +check "bundler" bundler --version +check "github-pages" github-pages --version +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" + +# Report result +reportResults diff --git a/src/miniconda/.devcontainer.json b/src/miniconda/.devcontainer.json index 0d5a301765..5605bb6070 100644 --- a/src/miniconda/.devcontainer.json +++ b/src/miniconda/.devcontainer.json @@ -1,16 +1,12 @@ { - "build": { - "dockerfile": "./Dockerfile", - "context": "." - }, - "features": [ - { - "id": "devcontainers/features/python@v0.0.2", - "options": { - "version": "none" - } - } - ], + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "features": { + "devcontainers/features/python@v0.0.2": "none", + "devcontainers/features/node@v0.0.2": "none" + }, // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. @@ -45,4 +41,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/miniconda/devcontainers-cli-0.6.4.tgz b/src/miniconda/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/miniconda/devcontainers-cli-0.6.4.tgz differ diff --git a/src/php/.devcontainer.json b/src/php/.devcontainer.json index c7b9e10f6e..aaa68294ee 100644 --- a/src/php/.devcontainer.json +++ b/src/php/.devcontainer.json @@ -3,27 +3,19 @@ "dockerfile": "./Dockerfile", "context": "." }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } + "features": { + "devcontainers/features/common@v0.0.2": { + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" }, - { - "id": "./local-features/apache-config", - "options": {} - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "none" - } - } + "./local-features/apache-config": "latest", + "devcontainers/features/node@v0.0.2": "none" + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2" ], // Configure tool-specific properties. "customizations": { @@ -50,4 +42,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/php/devcontainers-cli-0.6.4.tgz b/src/php/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/php/devcontainers-cli-0.6.4.tgz differ diff --git a/src/php/test-project/test.sh b/src/php/test-project/test.sh old mode 100644 new mode 100755 index 9b98770efe..7f91a48f45 --- a/src/php/test-project/test.sh +++ b/src/php/test-project/test.sh @@ -10,5 +10,9 @@ checkCommon check "php" php --version check "apache2ctl" which apache2ctl +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" +check "yarn" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version" + # Report result reportResults diff --git a/src/python/.devcontainer.json b/src/python/.devcontainer.json index 7dea4717e9..cc6c7d15bd 100644 --- a/src/python/.devcontainer.json +++ b/src/python/.devcontainer.json @@ -1,32 +1,22 @@ { - "build": { - "dockerfile": "./Dockerfile", - "context": "." - }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } - }, - { - "id": "devcontainers/features/python@v0.0.2", - "options": { - "version": "none" - } - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "none" - } - } - ], + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "features": { + "devcontainers/features/common@v0.0.2":{ + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" + }, + "devcontainers/features/python@v0.0.2": "none", + "devcontainers/features/node@v0.0.2": "none" + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2" + ], // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. @@ -61,4 +51,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/python/devcontainers-cli-0.6.4.tgz b/src/python/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/python/devcontainers-cli-0.6.4.tgz differ diff --git a/src/python/test-project/test-utils.sh b/src/python/test-project/test-utils.sh new file mode 100644 index 0000000000..6994afdf70 --- /dev/null +++ b/src/python/test-project/test-utils.sh @@ -0,0 +1,148 @@ +#!/bin/bash +SCRIPT_FOLDER="$(cd "$(dirname $0)" && pwd)" +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +echoStderr() +{ + echo "$@" 1>&2 +} + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echoStderr -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echoStderr -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} + +fixTestProjectFolderPrivs() { + if [ "${USERNAME}" != "root" ]; then + TEST_PROJECT_FOLDER="${1:-$SCRIPT_FOLDER}" + FOLDER_USER="$(stat -c '%U' "${TEST_PROJECT_FOLDER}")" + if [ "${FOLDER_USER}" != "${USERNAME}" ]; then + echoStderr "WARNING: Test project folder is owned by ${FOLDER_USER}. Updating to ${USERNAME}." + sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" + fi + fi +} diff --git a/src/python/test-project/test.sh b/src/python/test-project/test.sh new file mode 100755 index 0000000000..cbc0f615a3 --- /dev/null +++ b/src/python/test-project/test.sh @@ -0,0 +1,28 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Image specific tests +check "python" python --version +check "pipx" pipx --version +check "pylint" pylint --version +check "flake8" flake8 --version +check "autopep8" autopep8 --version +check "yapf" yapf --version +check "mypy" mypy --version +check "pydocstyle" pydocstyle --version +check "pycodestyle" pycodestyle --version +check "bandit" bandit --version +check "pipenv" pipenv --version +check "virtualenv" virtualenv --version + +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" +check "yarn" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version" + +# Report result +reportResults diff --git a/src/ruby/.devcontainer.json b/src/ruby/.devcontainer.json index ccd282f41e..ee772e46bf 100644 --- a/src/ruby/.devcontainer.json +++ b/src/ruby/.devcontainer.json @@ -3,29 +3,19 @@ "dockerfile": "./Dockerfile", "context": "." }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } + "features": { + "devcontainers/features/common@v0.0.2": { + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" }, - { - "id": "devcontainers/features/ruby@v0.0.2", - "options": { - "version": "none" - } - }, - { - "id": "devcontainers/features/node@v0.0.2", - "options": { - "version": "none" - } - } + "devcontainers/features/ruby@v0.0.2": "none", + "devcontainers/features/node@v0.0.2": "none" + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2" ], // Configure tool-specific properties. "customizations": { @@ -45,4 +35,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/ruby/devcontainers-cli-0.6.4.tgz b/src/ruby/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/ruby/devcontainers-cli-0.6.4.tgz differ diff --git a/src/ruby/test-project/test-utils.sh b/src/ruby/test-project/test-utils.sh new file mode 100644 index 0000000000..6994afdf70 --- /dev/null +++ b/src/ruby/test-project/test-utils.sh @@ -0,0 +1,148 @@ +#!/bin/bash +SCRIPT_FOLDER="$(cd "$(dirname $0)" && pwd)" +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +echoStderr() +{ + echo "$@" 1>&2 +} + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echoStderr -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echoStderr -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} + +fixTestProjectFolderPrivs() { + if [ "${USERNAME}" != "root" ]; then + TEST_PROJECT_FOLDER="${1:-$SCRIPT_FOLDER}" + FOLDER_USER="$(stat -c '%U' "${TEST_PROJECT_FOLDER}")" + if [ "${FOLDER_USER}" != "${USERNAME}" ]; then + echoStderr "WARNING: Test project folder is owned by ${FOLDER_USER}. Updating to ${USERNAME}." + sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" + fi + fi +} diff --git a/src/ruby/test-project/test.sh b/src/ruby/test-project/test.sh new file mode 100755 index 0000000000..b67edfb227 --- /dev/null +++ b/src/ruby/test-project/test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Image specific tests +check "ruby" ruby --version +check "rvm" rvm --version +check "gem" gem --version + +check "nvm" bash -c ". /usr/local/share/nvm/nvm.sh && nvm install 10" +check "nvm-node" bash -c ". /usr/local/share/nvm/nvm.sh && node --version" +check "yarn" bash -c ". /usr/local/share/nvm/nvm.sh && yarn --version" + +# Report result +reportResults diff --git a/src/rust/.devcontainer.json b/src/rust/.devcontainer.json index 7ce25b13ff..a28e4f0069 100644 --- a/src/rust/.devcontainer.json +++ b/src/rust/.devcontainer.json @@ -1,26 +1,21 @@ { - "build": { - "dockerfile": "./Dockerfile", - "context": "." - }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } - }, - { - "id": "devcontainers/features/rust@v0.0.2", - "options": { - "version": "latest" - } - } - ], + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "features": { + "devcontainers/features/common@v0.0.2": { + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" + }, + "devcontainers/features/rust@v0.0.2": "latest" + }, + "overrideFeatureInstallOrder": [ + "devcontainers/features/common@v0.0.2" + ], // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. @@ -52,4 +47,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/rust/devcontainers-cli-0.6.4.tgz b/src/rust/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/rust/devcontainers-cli-0.6.4.tgz differ diff --git a/src/rust/test-project/test-utils.sh b/src/rust/test-project/test-utils.sh new file mode 100644 index 0000000000..6994afdf70 --- /dev/null +++ b/src/rust/test-project/test-utils.sh @@ -0,0 +1,148 @@ +#!/bin/bash +SCRIPT_FOLDER="$(cd "$(dirname $0)" && pwd)" +USERNAME=${1:-vscode} + +if [ -z $HOME ]; then + HOME="/root" +fi + +FAILED=() + +echoStderr() +{ + echo "$@" 1>&2 +} + +check() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkMultiple() { + PASSED=0 + LABEL="$1" + echo -e "\n🧪 Testing $LABEL." + shift; MINIMUMPASSED=$1 + shift; EXPRESSION="$1" + while [ "$EXPRESSION" != "" ]; do + if $EXPRESSION; then ((PASSED++)); fi + shift; EXPRESSION=$1 + done + if [ $PASSED -ge $MINIMUMPASSED ]; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkOSPackages() { + LABEL=$1 + shift + echo -e "\n🧪 Testing $LABEL" + if dpkg-query --show -f='${Package}: ${Version}\n' "$@"; then + echo "✅ Passed!" + return 0 + else + echoStderr "❌ $LABEL check failed." + FAILED+=("$LABEL") + return 1 + fi +} + +checkExtension() { + # Happens asynchronusly, so keep retrying 10 times with an increasing delay + EXTN_ID="$1" + TIMEOUT_SECONDS="${2:-10}" + RETRY_COUNT=0 + echo -e -n "\n🧪 Looking for extension $1 for maximum of ${TIMEOUT_SECONDS}s" + until [ "${RETRY_COUNT}" -eq "${TIMEOUT_SECONDS}" ] || \ + [ ! -e $HOME/.vscode-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-server-insiders/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-test-server/extensions/${EXTN_ID}* ] || \ + [ ! -e $HOME/.vscode-remote/extensions/${EXTN_ID}* ] + do + sleep 1s + (( RETRY_COUNT++ )) + echo -n "." + done + + if [ ${RETRY_COUNT} -lt ${TIMEOUT_SECONDS} ]; then + echo -e "\n✅ Passed!" + return 0 + else + echoStderr -e "\n❌ Extension $EXTN_ID not found." + FAILED+=("$LABEL") + return 1 + fi +} + +checkCommon() +{ + PACKAGE_LIST="apt-utils \ + git \ + openssh-client \ + less \ + iproute2 \ + procps \ + curl \ + wget \ + unzip \ + nano \ + jq \ + lsb-release \ + ca-certificates \ + apt-transport-https \ + dialog \ + gnupg2 \ + libc6 \ + libgcc1 \ + libgssapi-krb5-2 \ + liblttng-ust0 \ + libstdc++6 \ + zlib1g \ + locales \ + sudo" + + # Actual tests + checkOSPackages "common-os-packages" ${PACKAGE_LIST} + check "non-root-user" id ${USERNAME} + check "locale" [ $(locale -a | grep en_US.utf8) ] + check "sudo" sudo echo "sudo works." + check "zsh" zsh --version + check "oh-my-zsh" [ -d "$HOME/.oh-my-zsh" ] + check "login-shell-path" [ -f "/etc/profile.d/00-restore-env.sh" ] + check "code" which code +} + +reportResults() { + if [ ${#FAILED[@]} -ne 0 ]; then + echoStderr -e "\n💥 Failed tests: ${FAILED[@]}" + exit 1 + else + echo -e "\n💯 All passed!" + exit 0 + fi +} + +fixTestProjectFolderPrivs() { + if [ "${USERNAME}" != "root" ]; then + TEST_PROJECT_FOLDER="${1:-$SCRIPT_FOLDER}" + FOLDER_USER="$(stat -c '%U' "${TEST_PROJECT_FOLDER}")" + if [ "${FOLDER_USER}" != "${USERNAME}" ]; then + echoStderr "WARNING: Test project folder is owned by ${FOLDER_USER}. Updating to ${USERNAME}." + sudo chown -R ${USERNAME} "${TEST_PROJECT_FOLDER}" + fi + fi +} diff --git a/src/rust/test-project/test.sh b/src/rust/test-project/test.sh new file mode 100755 index 0000000000..f5fd1624e4 --- /dev/null +++ b/src/rust/test-project/test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +cd $(dirname "$0") + +source test-utils.sh vscode + +# Run common tests +checkCommon + +# Image specific tests +check "rust" rustc --version +check "cargo" cargo --version + +# Report result +reportResults diff --git a/src/ubuntu/.devcontainer.json b/src/ubuntu/.devcontainer.json index 2fa0b69083..753ef4f535 100644 --- a/src/ubuntu/.devcontainer.json +++ b/src/ubuntu/.devcontainer.json @@ -3,18 +3,15 @@ "dockerfile": "./Dockerfile", "context": "." }, - "features": [ - { - "id": "devcontainers/features/common@v0.0.2", - "options": { - "install_Zsh": "true", - "username": "vscode", - "user_uid": "1000", - "user_gid": "1000", - "upgrade_packages": "true" - } + "features": { + "devcontainers/features/common@v0.0.2": { + "install_Zsh": "true", + "username": "vscode", + "user_uid": "1000", + "user_gid": "1000", + "upgrade_packages": "true" } - ], + }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], @@ -23,4 +20,4 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode" -} \ No newline at end of file +} diff --git a/src/ubuntu/devcontainers-cli-0.6.4.tgz b/src/ubuntu/devcontainers-cli-0.6.4.tgz new file mode 100755 index 0000000000..c67f622359 Binary files /dev/null and b/src/ubuntu/devcontainers-cli-0.6.4.tgz differ