From 0a53681abb89957ce4e6ff14728a71dec810dc84 Mon Sep 17 00:00:00 2001 From: matdave Date: Thu, 10 Sep 2026 13:42:04 -0500 Subject: [PATCH] adding valkey feature --- src/valkey/README.md | 44 +++++++ src/valkey/devcontainer-feature.json | 20 ++++ src/valkey/install.sh | 74 ++++++++++++ src/valkey/library_scripts.sh | 173 +++++++++++++++++++++++++++ test/valkey/scenarios.json | 16 +++ test/valkey/test.sh | 10 ++ test/valkey/test_debian.sh | 10 ++ test/valkey/test_specific_version.sh | 9 ++ 8 files changed, 356 insertions(+) create mode 100644 src/valkey/README.md create mode 100644 src/valkey/devcontainer-feature.json create mode 100755 src/valkey/install.sh create mode 100644 src/valkey/library_scripts.sh create mode 100644 test/valkey/scenarios.json create mode 100755 test/valkey/test.sh create mode 100755 test/valkey/test_debian.sh create mode 100755 test/valkey/test_specific_version.sh diff --git a/src/valkey/README.md b/src/valkey/README.md new file mode 100644 index 000000000..d0735b554 --- /dev/null +++ b/src/valkey/README.md @@ -0,0 +1,44 @@ +# valkey (via build from source) (valkey) + +Valkey is an open source (BSD) high performance key/value datastore. Installs valkey-server and valkey-cli by building from source, following the official valkey-container Dockerfile. + +## Example Usage + +```json +"features": { + "ghcr.io/devcontainers-extra/features/valkey:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Select the version to install (full version tag, e.g. 8.1.10, or 'latest'). | string | latest | + +## Starting the Server + +This feature only installs the binaries; it does not start a daemon. To start `valkey-server` automatically each time the container starts, add a `postStartCommand` to your `devcontainer.json`: + +```json +{ + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "ghcr.io/devcontainers-extra/features/valkey:1": {} + }, + "postStartCommand": "valkey-server --daemonize yes" +} +``` + +`--daemonize yes` backgrounds the server (it would otherwise block the postStartCommand). Connect with `valkey-cli` or `valkey-cli ping`. + +## Notes + +- Valkey does not publish prebuilt binaries, only source tarballs, so this feature compiles from source, mirroring the [official valkey-container Dockerfile](https://github.com/valkey-io/valkey-container/blob/mainline/Dockerfile.template). +- The downloaded tarball is verified against the [official valkey-hashes file](https://github.com/valkey-io/valkey-hashes). +- TLS support is enabled (`BUILD_TLS=yes`). +- Start the server manually with `valkey-server` and connect with `valkey-cli`. + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._ \ No newline at end of file diff --git a/src/valkey/devcontainer-feature.json b/src/valkey/devcontainer-feature.json new file mode 100644 index 000000000..4f948ec1e --- /dev/null +++ b/src/valkey/devcontainer-feature.json @@ -0,0 +1,20 @@ +{ + "id": "valkey", + "version": "1.0.0", + "name": "valkey (via build from source)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/valkey", + "description": "Valkey is an open source (BSD) high performance key/value datastore. Installs valkey-server and valkey-cli by building from source, following the official valkey-container Dockerfile.", + "options": { + "version": { + "default": "latest", + "description": "Select the version to install (full version tag, e.g. 8.1.10, or 'latest').", + "proposals": [ + "latest", + "8.1.10", + "9.0.6" + ], + "type": "string" + } + }, + "installsAfter": [] +} \ No newline at end of file diff --git a/src/valkey/install.sh b/src/valkey/install.sh new file mode 100755 index 000000000..d4d89b948 --- /dev/null +++ b/src/valkey/install.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +VALKEY_VERSION="${VERSION:-"latest"}" + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# valkey does not publish prebuilt binaries, only source tarballs, so we build +# from source, following the official valkey-container Dockerfile: +# https://github.com/valkey-io/valkey-container/blob/mainline/Dockerfile.template + +# make sure we have curl and jq for resolving the latest version +if ! type curl >/dev/null 2>&1 || ! type jq >/dev/null 2>&1; then + if [ -x "/usr/bin/apt-get" ]; then + $nanolayer_location install apt-get "curl jq" + elif [ -x "/sbin/apk" ]; then + apk add --no-cache curl jq + else + echo "distro not supported" + exit 1 + fi +fi + +# resolve "latest" to the newest stable release tag +if [[ "$VALKEY_VERSION" == "latest" ]]; then + VALKEY_VERSION="$(curl -sfL https://api.github.com/repos/valkey-io/valkey/releases/latest | jq -r '.tag_name')" +fi + +# download the source tarball (clean_download minimizes layer leftovers) +TARBALL_URL="https://github.com/valkey-io/valkey/archive/refs/tags/${VALKEY_VERSION}.tar.gz" +clean_download "$TARBALL_URL" /tmp/valkey.tar.gz + +# verify the tarball against the official valkey-hashes file (same as valkey-container) +# format: hash (the last match wins, in case of re-releases) +HASHES_URL="https://raw.githubusercontent.com/valkey-io/valkey-hashes/main/README" +EXPECTED_SHA="$(curl -sfL "$HASHES_URL" | awk -v f="valkey-${VALKEY_VERSION}.tar.gz" '$2 == f { sha = $4 } END { print sha }')" +if [[ -n "$EXPECTED_SHA" ]]; then + echo "$EXPECTED_SHA /tmp/valkey.tar.gz" | sha256sum -c - +else + echo "warning: no sha256 found in valkey-hashes for $VALKEY_VERSION, skipping verification" +fi + +# install build dependencies (mirrors the valkey-container build stage; systemd is +# skipped since dev containers do not run systemd) +if [ -x "/usr/bin/apt-get" ]; then + $nanolayer_location install apt-get "dpkg-dev gcc libc6-dev libssl-dev make" +elif [ -x "/sbin/apk" ]; then + apk add --no-cache build-base linux-headers openssl-dev +else + echo "distro not supported" + exit 1 +fi + +# extract and build with TLS support +mkdir -p /usr/src/valkey +tar -xzf /tmp/valkey.tar.gz -C /usr/src/valkey --strip-components=1 +rm /tmp/valkey.tar.gz + +export BUILD_TLS=yes +make -C /usr/src/valkey -j "$(nproc)" all +make -C /usr/src/valkey install + +# cleanup the build tree +rm -rf /usr/src/valkey + +echo 'Done!' \ No newline at end of file diff --git a/src/valkey/library_scripts.sh b/src/valkey/library_scripts.sh new file mode 100644 index 000000000..f6d0760d7 --- /dev/null +++ b/src/valkey/library_scripts.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +clean_download() { + # The purpose of this function is to download a file with minimal impact on container layer size + # this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a + # temporary manner, and making sure to + # 1. uninstall the downloader at the return of the function + # 2. revert back any changes to the package installer database/cache (for example apt-get lists) + # The above steps will minimize the leftovers being created while installing the downloader + # Supported distros: + # debian/ubuntu/alpine + + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + function _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize contianer layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + function _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + function _apk_install() { + tempdir=$1 + # copy current state of apk cache - in order to revert back later (minimize contianer layer size) + cp -p -R /var/cache/apk $tempdir + + apk add --no-cache wget + } + + function _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + # try to use either wget or curl if one of them already installer + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/test/valkey/scenarios.json b/test/valkey/scenarios.json new file mode 100644 index 000000000..420a7265f --- /dev/null +++ b/test/valkey/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "valkey": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "valkey": { + "version": "8.1.10" + } + } + } +} \ No newline at end of file diff --git a/test/valkey/test.sh b/test/valkey/test.sh new file mode 100755 index 000000000..20d2b9802 --- /dev/null +++ b/test/valkey/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "valkey-server is installed" valkey-server --version +check "valkey-cli is installed" valkey-cli --version + +reportResults \ No newline at end of file diff --git a/test/valkey/test_debian.sh b/test/valkey/test_debian.sh new file mode 100755 index 000000000..20d2b9802 --- /dev/null +++ b/test/valkey/test_debian.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "valkey-server is installed" valkey-server --version +check "valkey-cli is installed" valkey-cli --version + +reportResults \ No newline at end of file diff --git a/test/valkey/test_specific_version.sh b/test/valkey/test_specific_version.sh new file mode 100755 index 000000000..408019b11 --- /dev/null +++ b/test/valkey/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "valkey version is equal to 8.1.10" sh -c "valkey-server --version | grep 'v=8.1.10'" + +reportResults \ No newline at end of file