From bf88846218a3b2f55f1f249385a48a45bd62ca5f Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 16 Oct 2026 20:41:52 +0300 Subject: [PATCH] feat(dx): Dev Container Feature that installs querya-ext-tester (#1487, part 3a) Downloads the tester from a release, checks SHA-256 against SHA256SUMS.txt, refuses releases before 0.4.19. Tested on Ubuntu and Debian bases plus a pinned-version scenario; published to ghcr.io/queryahub/features from main/dev. --- .github/workflows/devcontainer-feature.yml | 74 +++++++++++++++++++ .../devcontainer-feature.json | 18 +++++ .../src/querya-ext-tester/install.sh | 69 +++++++++++++++++ .../test/querya-ext-tester/pinned.sh | 9 +++ .../test/querya-ext-tester/scenarios.json | 8 ++ .../test/querya-ext-tester/test.sh | 10 +++ docs/extension-testing.md | 19 +++++ 7 files changed, 207 insertions(+) create mode 100644 .github/workflows/devcontainer-feature.yml create mode 100644 devcontainer-features/src/querya-ext-tester/devcontainer-feature.json create mode 100755 devcontainer-features/src/querya-ext-tester/install.sh create mode 100755 devcontainer-features/test/querya-ext-tester/pinned.sh create mode 100644 devcontainer-features/test/querya-ext-tester/scenarios.json create mode 100755 devcontainer-features/test/querya-ext-tester/test.sh diff --git a/.github/workflows/devcontainer-feature.yml b/.github/workflows/devcontainer-feature.yml new file mode 100644 index 00000000..8d3aea1e --- /dev/null +++ b/.github/workflows/devcontainer-feature.yml @@ -0,0 +1,74 @@ +name: Dev Container Feature + +# Tests and publishes ghcr.io/queryahub/features/querya-ext-tester +# (devcontainer-features/src). The tester is a glibc binary, so only +# Debian-family bases are tested. Pull requests only test; the Feature is pushed +# from main/dev or by hand. +on: + push: + branches: [main, dev] + paths: + - devcontainer-features/** + - .github/workflows/devcontainer-feature.yml + pull_request: + paths: + - devcontainer-features/** + - .github/workflows/devcontainer-feature.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: devcontainer-feature-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Test querya-ext-tester Feature (${{ matrix.base }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + base: + - mcr.microsoft.com/devcontainers/base:ubuntu + - mcr.microsoft.com/devcontainers/base:debian + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install the Dev Containers CLI + run: npm install -g @devcontainers/cli@0.80.0 + - name: Autogenerated tests (default options) + run: | + devcontainer features test \ + --features querya-ext-tester \ + --base-image "${{ matrix.base }}" \ + --project-folder devcontainer-features + - name: Scenarios + if: matrix.base == 'mcr.microsoft.com/devcontainers/base:ubuntu' + run: | + devcontainer features test \ + --features querya-ext-tester \ + --skip-autogenerated \ + --project-folder devcontainer-features + + publish: + name: Publish Feature + needs: test + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 + - uses: devcontainers/action@v1 + with: + publish-features: "true" + base-path-to-features: ./devcontainer-features/src + features-namespace: queryahub/features + generate-docs: "false" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/devcontainer-features/src/querya-ext-tester/devcontainer-feature.json b/devcontainer-features/src/querya-ext-tester/devcontainer-feature.json new file mode 100644 index 00000000..314fd79c --- /dev/null +++ b/devcontainer-features/src/querya-ext-tester/devcontainer-feature.json @@ -0,0 +1,18 @@ +{ + "id": "querya-ext-tester", + "version": "1.0.0", + "name": "Querya extension tester", + "description": "Installs querya-ext-tester, the headless test runner for Querya database-driver extensions, from a Querya Desktop release (SHA-256 checked).", + "documentationURL": "https://github.com/QueryaHub/Querya-Desktop/blob/main/docs/extension-testing.md", + "options": { + "version": { + "type": "string", + "default": "latest", + "proposals": ["latest", "0.4.19"], + "description": "Querya Desktop release to take the tester from (for example 0.4.19), or latest. Must be 0.4.19 or newer." + } + }, + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/devcontainer-features/src/querya-ext-tester/install.sh b/devcontainer-features/src/querya-ext-tester/install.sh new file mode 100755 index 00000000..ac07a0f2 --- /dev/null +++ b/devcontainer-features/src/querya-ext-tester/install.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Installs querya-ext-tester from a Querya Desktop release and checks the +# archive against the SHA256SUMS.txt published with that release. +set -euo pipefail + +REPO="QueryaHub/Querya-Desktop" +MIN_VERSION="0.4.19" # earlier testers ignored the exit code of main() +REQUESTED="${VERSION:-latest}" +DEST="${QUERYA_EXT_TESTER_DEST:-/usr/local/bin/querya-ext-tester}" + +fail() { echo "querya-ext-tester feature: $*" >&2; exit 1; } + +ensure_tools() { + local missing=() + for tool in curl tar sha256sum; do + command -v "$tool" >/dev/null 2>&1 || missing+=("$tool") + done + [ ${#missing[@]} -eq 0 ] && return 0 + if command -v apt-get >/dev/null 2>&1; then + apt-get update -y && apt-get install -y --no-install-recommends curl ca-certificates tar coreutils + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache curl ca-certificates tar coreutils + elif command -v dnf >/dev/null 2>&1; then + dnf install -y curl ca-certificates tar coreutils + else + fail "missing ${missing[*]} and no known package manager to install them" + fi +} + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) TARGET="linux-x64" ;; + *) fail "only Linux x86_64 is released ($(uname -s)-$(uname -m))" ;; +esac + +ensure_tools + +if [ "$REQUESTED" = "latest" ]; then + # The redirect avoids the API and its unauthenticated rate limit. + TAG="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest")" + TAG="${TAG##*/}" +else + TAG="${REQUESTED#v}" +fi +VERSION_NUMBER="${TAG#v}" +[[ "$VERSION_NUMBER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "cannot read a version from '${TAG}'" + +if [ "$(printf '%s\n%s\n' "$MIN_VERSION" "$VERSION_NUMBER" | sort -V | head -n1)" != "$MIN_VERSION" ]; then + fail "querya-ext-tester ${VERSION_NUMBER} is older than ${MIN_VERSION}; its exit codes are not reliable" +fi + +ASSET="querya-ext-tester-v${VERSION_NUMBER}-${TARGET}.tar.gz" +BASE="https://github.com/${REPO}/releases/download/${TAG}" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +echo "Downloading ${ASSET} from release ${TAG}" +curl -fsSL -o "${WORK}/${ASSET}" "${BASE}/${ASSET}" || fail "cannot download ${BASE}/${ASSET}" +curl -fsSL -o "${WORK}/SHA256SUMS.txt" "${BASE}/SHA256SUMS.txt" || fail "cannot download ${BASE}/SHA256SUMS.txt" + +EXPECTED="$(grep " ${ASSET}\$" "${WORK}/SHA256SUMS.txt" | cut -d' ' -f1 || true)" +[ -n "$EXPECTED" ] || fail "${ASSET} is not listed in SHA256SUMS.txt of ${TAG}" +ACTUAL="$(sha256sum "${WORK}/${ASSET}" | cut -d' ' -f1)" +[ "$EXPECTED" = "$ACTUAL" ] || fail "checksum mismatch for ${ASSET}" + +tar -xzf "${WORK}/${ASSET}" -C "$WORK" +install -m 0755 "${WORK}/querya-ext-tester" "$DEST" + +"$DEST" --help | grep -q "Usage: querya-ext-tester" || fail "installed binary does not run" +echo "Installed querya-ext-tester ${VERSION_NUMBER}" diff --git a/devcontainer-features/test/querya-ext-tester/pinned.sh b/devcontainer-features/test/querya-ext-tester/pinned.sh new file mode 100755 index 00000000..fad349d4 --- /dev/null +++ b/devcontainer-features/test/querya-ext-tester/pinned.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Scenario "pinned": version 0.4.19, the oldest supported tester. +set -e +source dev-container-features-test-lib + +check "tester is on PATH" command -v querya-ext-tester +check "tester prints usage" bash -c 'querya-ext-tester --help | grep -q "Usage: querya-ext-tester"' + +reportResults diff --git a/devcontainer-features/test/querya-ext-tester/scenarios.json b/devcontainer-features/test/querya-ext-tester/scenarios.json new file mode 100644 index 00000000..770aeea7 --- /dev/null +++ b/devcontainer-features/test/querya-ext-tester/scenarios.json @@ -0,0 +1,8 @@ +{ + "pinned": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "querya-ext-tester": { "version": "0.4.19" } + } + } +} diff --git a/devcontainer-features/test/querya-ext-tester/test.sh b/devcontainer-features/test/querya-ext-tester/test.sh new file mode 100755 index 00000000..ce0c28b2 --- /dev/null +++ b/devcontainer-features/test/querya-ext-tester/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Default scenario: the Feature with its default options (latest release). +set -e +source dev-container-features-test-lib + +check "tester is on PATH" command -v querya-ext-tester +check "tester prints usage" bash -c 'querya-ext-tester --help | grep -q "Usage: querya-ext-tester"' +check "tester fails on an empty directory" bash -c 'd=$(mktemp -d); ! querya-ext-tester "$d" --no-color' + +reportResults diff --git a/docs/extension-testing.md b/docs/extension-testing.md index bdc6b924..e7922504 100644 --- a/docs/extension-testing.md +++ b/docs/extension-testing.md @@ -177,6 +177,25 @@ the container with `--security-opt seccomp=unconfined` (and, on hosts that restrict namespaces with AppArmor, `--security-opt apparmor=unconfined`), or run the tester directly on the CI runner with the action below. +## Dev Container Feature + +In a dev container the tester can be installed as a Feature. It downloads the +release archive, checks it against `SHA256SUMS.txt` and puts `querya-ext-tester` +in `PATH` (Linux x86_64, Debian-family images): + +```json +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/queryahub/features/querya-ext-tester:1": { "version": "latest" } + } +} +``` + +`version` is a Querya Desktop release (`0.4.19`) or `latest`. Releases before +0.4.19 are refused: their exit codes were not reliable. The source is in +`devcontainer-features/src/querya-ext-tester`. + ## GitHub Actions This repository contains a composite action in